/*
Copyright 2006-2010 Stefano Chizzolini. http://www.pdfclown.org
Contributors:
* Stefano Chizzolini (original code developer, http://www.stefanochizzolini.it)
This file should be part of the source code distribution of "PDF Clown library"
(the Program): see the accompanying README files for more info.
This Program is free software; you can redistribute it and/or modify it under the terms
of the GNU Lesser General Public License as published by the Free Software Foundation;
either version 3 of the License, or (at your option) any later version.
This Program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY,
either expressed or implied; without even the implied warranty of MERCHANTABILITY or
FITNESS FOR A PARTICULAR PURPOSE. See the License for more details.
You should have received a copy of the GNU Lesser General Public License along with this
Program (see README files); if not, go to the GNU website (http://www.gnu.org/licenses/).
Redistribution and use, with or without modification, are permitted provided that such
redistributions retain the above copyright notice, license and disclaimer, along with
this list of conditions.
*/
package org.pdfclown.files;
import java.io.Closeable;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.util.Random;
import org.pdfclown.Version;
import org.pdfclown.VersionEnum;
import org.pdfclown.bytes.FileInputStream;
import org.pdfclown.bytes.IInputStream;
import org.pdfclown.bytes.IOutputStream;
import org.pdfclown.bytes.OutputStream;
import org.pdfclown.documents.Document;
import org.pdfclown.documents.Document.Configuration.XRefModeEnum;
import org.pdfclown.objects.IPdfIndirectObject;
import org.pdfclown.objects.PdfDataObject;
import org.pdfclown.objects.PdfDictionary;
import org.pdfclown.objects.PdfDirectObject;
import org.pdfclown.objects.PdfIndirectObject;
import org.pdfclown.objects.PdfName;
import org.pdfclown.objects.PdfObject;
import org.pdfclown.objects.PdfReference;
import org.pdfclown.tokens.FileFormatException;
import org.pdfclown.tokens.Reader;
import org.pdfclown.tokens.Reader.FileInfo;
import org.pdfclown.tokens.Writer;
import org.pdfclown.util.NotImplementedException;
/**
PDF file representation.
@author Stefano Chizzolini (http://www.stefanochizzolini.it)
@since 0.0.0
@version 0.1.0
*/
public final class File
implements Closeable
{
// <class>
// <classes>
public static final class ResolvedObject<T extends PdfDataObject>
{
public final T dataObject;
public final PdfIndirectObject container;
private ResolvedObject(
T dataObject,
PdfIndirectObject container
)
{
this.dataObject = dataObject;
this.container = container;
}
}
// </classes>
// <static>
// <fields>
private static Random hashCodeGenerator = new Random();
// </fields>
// <interface>
// <public>
/**
Forces a generic object to be expressed as its corresponding data object.
*/
public static PdfDataObject resolve(
PdfObject object
)
{
if(object instanceof IPdfIndirectObject)
return ((IPdfIndirectObject)object).getDataObject();
else
return (PdfDataObject)object;
}
/**
Resolves a generic object.
*/
@SuppressWarnings("unchecked")
public static <T extends PdfDataObject> ResolvedObject<T> resolve(
PdfObject object,
IPdfIndirectObject container
)
{
if(object == null)
return null;
else if(object instanceof IPdfIndirectObject)
{
IPdfIndirectObject indirectObject = (IPdfIndirectObject)object;
return new ResolvedObject<T>((T)indirectObject.getDataObject(),indirectObject.getIndirectObject());
}
else
return new ResolvedObject<T>((T)object,container.getIndirectObject());
}
/**
Forces a direct object to be updated (whether possible).
*/
public static boolean update(
PdfDirectObject object
)
{
/*
NOTE: Only PDF references are able to be updated. Other direct types
are dependent on their respective containers for update.
*/
if(object instanceof PdfReference)
{
((PdfReference)object).getIndirectObject().update();
return true;
}
else
return false;
}
// </public>
// </interface>
// </static>
// <dynamic>
// <fields>
private Document document;
private int hashCode = hashCodeGenerator.nextInt();
private IndirectObjects indirectObjects;
private String path;
private Reader reader;
private PdfDictionary trailer;
private Version version;
// </fields>
// <constructors>
public File(
)
{
version = VersionEnum.PDF14.getVersion();
trailer = new PdfDictionary();
indirectObjects = new IndirectObjects(this, null);
document = new Document(this);
}
public File(
String path
) throws FileFormatException,
java.io.FileNotFoundException
{
this(
new FileInputStream(
new java.io.RandomAccessFile(path,"r")
)
);
this.path = path;
}
public File(
IInputStream stream
) throws FileFormatException
{
reader = new Reader(stream, this);
FileInfo info = reader.readInfo();
version = info.getVersion();
trailer = info.getTrailer();
if(trailer.containsKey(PdfName.Encrypt)) // Encrypted file.
throw new NotImplementedException("Encrypted files are currently not supported.");
indirectObjects = new IndirectObjects(this, info.getXrefEntries());
document = new Document(trailer.get(PdfName.Root));
document.getConfiguration().setXrefMode(PdfName.XRef.equals(trailer.get(PdfName.Type)) ? XRefModeEnum.Compressed : XRefModeEnum.Plain);
}
// </constructors>
// <interface>
// <public>
/**
Gets the high-level representation of the file content.
*/
public Document getDocument(
)
{return document;}
/**
Gets the indirect objects collection.
*/
public IndirectObjects getIndirectObjects(
)
{return indirectObjects;}
/**
Gets the data reader backing this file.
@return <code>null</code> in case of newly-created file.
*/
public Reader getReader(
)
{return reader;}
/**
Gets the file trailer.
*/
public PdfDictionary getTrailer(
)
{return trailer;}
/**
Gets the file header version [PDF:1.6:3.4.1].
<p>This property represents just the original file version; to get the actual version,
use the {@link org.pdfclown.documents.Document#getVersion() Document.getVersion} method.</p>
*/
public Version getVersion(
)
{return version;}
@Override
public int hashCode(
)
{return hashCode;}
/**
Registers an <b>internal data object</b>.
@since 0.0.4
*/
public PdfReference register(
PdfDataObject object
)
{return indirectObjects.add(object).getReference();}
/**
Serializes the file to the current file-system path using the {@link SerializationModeEnum#Standard standard serialization mode}.
*/
public void save(
) throws IOException
{save(SerializationModeEnum.Standard);}
/**
Serializes the file to the current file-system path.
@param mode Serialization mode.
*/
public void save(
SerializationModeEnum mode
) throws IOException
{
if(!new java.io.File(path).exists())
throw new FileNotFoundException("No valid source path available.");
/*
NOTE: The document file cannot be directly overwritten
as it's locked for reading by the open stream;
its update is therefore delayed to its disposal,
when the temporary file will overwrite it (see close() method).
*/
save(getTempPath(), mode);
}
/**
Serializes the file to the specified file-system path.
@param path Target path.
@param mode Serialization mode.
*/
public void save(
String path,
SerializationModeEnum mode
) throws IOException
{
save(
new java.io.File(path),
mode
);
}
/**
Serializes the file to the specified file-system file.
@param file Target file.
@param mode Serialization mode.
*/
public void save(
java.io.File file,
SerializationModeEnum mode
) throws IOException
{
OutputStream outputStream;
java.io.BufferedOutputStream baseOutputStream;
try
{
file.createNewFile();
baseOutputStream = new java.io.BufferedOutputStream(
new java.io.FileOutputStream(file)
);
outputStream = new OutputStream(baseOutputStream);
}
catch(Exception e)
{throw new IOException(file.getPath() + " file couldn't be created.",e);}
try
{
save(
outputStream,
mode
);
baseOutputStream.flush();
baseOutputStream.close();
}
catch(Exception e)
{throw new IOException(file.getPath() + " file writing has failed.",e);}
}
/**
Serializes the file to the specified stream.
<p>It's caller responsibility to close the stream after this method ends.</p>
@param stream Target stream.
@param mode Serialization mode.
*/
public void save(
IOutputStream stream,
SerializationModeEnum mode
)
{
Writer writer = Writer.get(this, stream);
writer.write(mode);
}
/**
Unregisters an <b>internal object</b>.
@since 0.0.5
*/
public void unregister(
PdfReference reference
)
{indirectObjects.remove(reference.getObjectNumber());}
// <Closeable>
@Override
public void close(
) throws IOException
{
if(reader != null)
{
reader.close();
reader = null;
/*
NOTE: If the temporary file exists (see save() method),
it must overwrite the document file.
*/
java.io.File sourceFile = new java.io.File(getTempPath());
if(sourceFile.exists())
{
java.io.File targetFile = new java.io.File(path);
targetFile.delete();
sourceFile.renameTo(targetFile);
}
}
}
// </Closeable>
// </public>
// <protected>
@Override
protected void finalize(
) throws Throwable
{
try
{close();}
finally
{super.finalize();}
}
// </protected>
// <private>
private String getTempPath(
)
{return (path == null ? null : path + ".tmp");}
// </private>
// </interface>
// </dynamic>
// </class>
}