blob: ceb99e0e3c7aefafb46c64d5744faf0b506a356e [file] [log] [blame]
/*******************************************************************************
* Copyright (c) 1998, 2013 Oracle and/or its affiliates. All rights reserved.
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License v1.0 and Eclipse Distribution License v. 1.0
* which accompanies this distribution.
* The Eclipse Public License is available at http://www.eclipse.org/legal/epl-v10.html
* and the Eclipse Distribution License is available at
* http://www.eclipse.org/org/documents/edl-v10.php.
*
* Contributors:
* Oracle - initial API and implementation from Oracle TopLink
******************************************************************************/
package org.eclipse.persistence.tools.workbench.mappingsio;
import java.io.BufferedInputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.InputStream;
import java.util.Collection;
import java.util.Iterator;
import java.util.Vector;
import java.util.prefs.Preferences;
import org.eclipse.persistence.tools.workbench.mappingsio.legacy.LegacyIOFacade;
import org.eclipse.persistence.tools.workbench.mappingsmodel.MWModel;
import org.eclipse.persistence.tools.workbench.mappingsmodel.ProjectSubFileComponentContainer;
import org.eclipse.persistence.tools.workbench.mappingsmodel.project.MWProject;
import org.eclipse.persistence.tools.workbench.mappingsmodel.spi.DefaultSPIManager;
import org.eclipse.persistence.tools.workbench.mappingsmodel.spi.SPIManager;
import org.eclipse.persistence.tools.workbench.mappingsmodel.spi.SimpleSPIManager;
import org.eclipse.persistence.tools.workbench.mappingsmodel.spi.meta.classfile.CFExternalClassRepositoryFactory;
import org.eclipse.persistence.tools.workbench.utility.ClassTools;
import org.eclipse.persistence.tools.workbench.utility.CollectionTools;
import org.eclipse.persistence.tools.workbench.utility.XMLTools;
import org.eclipse.persistence.tools.workbench.utility.io.FileTools;
import org.eclipse.persistence.tools.workbench.utility.string.StringTools;
import org.w3c.dom.Document;
import org.w3c.dom.Node;
/**
* A new instance of this class is created for each project read:
* MWProject project = new ProjectReader(ioManager, file, preferences, listener).read()
*/
class ProjectReader {
/** The I/O manager that created this reader. */
private ProjectIOManager ioManager;
/** The project file to be read. */
private File file;
/** The user preferences used to configure the SPI manager. */
private Preferences preferences;
/** A listener that will be notified whenever an "expected" file is missing. */
private FileNotFoundListener listener;
/**
* A callback object that checks whether the reader
* should continue with the reading a legacy project.
*/
private LegacyProjectReadCallback legacyProjectReadCallback;
/** An XML document corresponding to the project file. */
private Document document;
/** The version of the schema describing the project file. */
private String schemaVersion;
/** @see MWProject#CURRENT_PROJECT_ROOT_ELEMENT_NAME */
private static final String PROJECT_ROOT_ELEMENT_NAME_4_X = "BldrProject";
/** @see MWProject#CURRENT_SCHEMA_VERSION_ELEMENT_NAME */
private static final String SCHEMA_VERSION_ELEMENT_NAME_5_0 = "schemaVersion"; // 9.0.4/10.0.0
private static final String SCHEMA_VERSION_ELEMENT_NAME_4_X = "version";
/** @see MWProject#CURRENT_SCHEMA_VERSION */
private static final String SCHEMA_VERSION_7_0 = "7.0"; //11.1.X.X
private static final String SCHEMA_VERSION_6_0 = "6.0"; // 10.1.3.X.X
private static final String SCHEMA_VERSION_5_X = "5"; // 9.0.4/10.0.0
private static final String SCHEMA_VERSION_4_5 = "4.5";
private static final String PRODUCT_VERSION_4_6 = "4.6"; // uses SCHEMA_VERSION_4_5
private static final String PRODUCT_VERSION_9_0_3 = "9.0.3"; // uses SCHEMA_VERSION_4_5
// ********** constructors **********
ProjectReader(ProjectIOManager ioManager, File file, Preferences preferences, FileNotFoundListener listener, LegacyProjectReadCallback legacyProjectReadCallback) {
super();
this.ioManager = ioManager;
this.file = file;
this.preferences = preferences;
this.listener = listener;
this.legacyProjectReadCallback = legacyProjectReadCallback;
}
// ********** public stuff **********
/**
* Read a project from the file, using the appropriate "schema".
* We will return null if we are reading a legacy project and the
* legacy project callback indicates we should not read the project.
*/
MWProject read() {
// pre-parse the file to find the schema version
this.document = XMLTools.parse(this.file);
this.schemaVersion = this.schemaVersion();
if (this.schemaVersion.equals(MWProject.CURRENT_SCHEMA_VERSION)) {
return this.readProject();
}
return this.readLegacyProject();
}
public String toString() {
return StringTools.buildToStringFor(this, this.file);
}
// ********** internal stuff **********
/**
* return the version of the schema used for the project file
* and associated xml files
* @see ProjectIOManager#CURRENT_SCHEMA_VERSION
*/
private String schemaVersion() {
Node rootNode = this.rootNode();
// first try the current element name
// then move back in time, trying previous element names
Node schemaVersionNode = XMLTools.child(rootNode, MWProject.CURRENT_SCHEMA_VERSION_ELEMENT_NAME);
if (schemaVersionNode == null) {
schemaVersionNode = XMLTools.child(rootNode, SCHEMA_VERSION_ELEMENT_NAME_5_0);
}
if (schemaVersionNode == null) {
schemaVersionNode = XMLTools.child(rootNode, SCHEMA_VERSION_ELEMENT_NAME_4_X);
}
if (schemaVersionNode == null) {
throw new IllegalArgumentException(); // must not be a valid project file...
}
return XMLTools.textContent(schemaVersionNode);
}
/**
* return the root node of the project file
* @see ProjectIOManager#CURRENT_PROJECT_XML_DOCUMENT_NAME
*/
private Node rootNode() {
// first try the current document name
// then move back in time, trying previous document names
Node rootNode = XMLTools.child(this.document, MWProject.CURRENT_PROJECT_ROOT_ELEMENT_NAME);
if (rootNode == null) {
rootNode = XMLTools.child(this.document, PROJECT_ROOT_ELEMENT_NAME_4_X);
}
if (rootNode == null) {
throw new IllegalArgumentException(); // must not be a valid project file...
}
return rootNode;
}
/**
* read a normal (non-legacy) project from the file and return it
*/
private MWProject readProject() {
// first read in the project, but none of its components (classes, metadata, descriptors)
MWProject project;
try {
project = (MWProject) this.readObject(this.file);
} catch (IOException ex) {
throw new RuntimeException(ex);
}
// we don't set the save directory on legacy projects,
// because it will be set by the user and saved if necessary
ClassTools.invokeMethod(project, "setSaveDirectoryForIOManager", File.class, this.baseDirectory());
// "inject" the SPIManager once we have the base project
this.injectSPIManager(project, new DefaultSPIManager(this.preferences, project.getName()));
// then use the names stored throughout the project to read up its components
SubComponentReader[] subComponentReaders = this.buildSubComponentReaders(project);
for (int i = 0; i < subComponentReaders.length; i++) {
subComponentReaders[i].read();
}
// now trigger all the handles to resolve etc.
project.postProjectBuild();
return project;
}
/**
* build readers for all the project's sub-components that are
* read in separately: classes, tables, descriptors
*/
private SubComponentReader[] buildSubComponentReaders(MWProject project) {
return new SubComponentReader[] {
new SubComponentReader(project.getClassRepository()),
new SubComponentReader(project.getMetaDataSubComponentContainer()),
new SubComponentReader(project.getDescriptorRepository()),
};
}
/**
* Use TopLink to unmarshal an object from the specified XML file.
* Let the exceptions through so we can swallow the FileNotFoundException
* when reading sub-components.
*/
Object readObject(File xmlFile) throws IOException {
InputStream stream = null;
Object object = null;
try {
stream = new BufferedInputStream(new FileInputStream(xmlFile));
object = this.ioManager.getUnmarshaller().unmarshal(stream);
} finally {
if (stream != null) {
stream.close();
}
}
return object;
}
/**
* return the base directory for all the project files;
* the project file is in this directory, while all the other
* files are in subdirectories of this directory
*/
File baseDirectory() {
return this.file.getParentFile();
}
String defaultFileNameExtension() {
return this.ioManager.defaultFileNameExtension();
}
String subDirectoryNameFor(Object container) {
return this.ioManager.subDirectoryNameFor(container);
}
void fireFileNotFound(File missingFile) {
this.ioManager.fireFileNotFound(this.listener, missingFile);
}
private void injectSPIManager(MWProject project, SPIManager spiManager) {
ClassTools.invokeMethod(project, "setSPIManagerForIOManager", SPIManager.class, spiManager);
}
private MWProject readLegacyProject() {
MWProject project;
if (this.schemaVersion.startsWith(SCHEMA_VERSION_6_0)) {
this.legacyProjectReadCallback.checkLegacyRead(this.schemaVersion);
project = LegacyIOFacade.read60Project(file, preferences);
} else {
// must be an unsupported version...
throw new IllegalStateException(this.schemaVersion);
}
// legacy projects are marked dirty, forcing the user to save it in a new location
project.markEntireBranchDirty();
project.setIsLegacyProject(true);
return project;
}
/**
* this SPI manager is used for pre-6.0 projects;
* it ignores any user preferences
*/
private SPIManager buildSimpleSPIManager() {
SimpleSPIManager mgr = new SimpleSPIManager();
mgr.setExternalClassRepositoryFactory(CFExternalClassRepositoryFactory.instance());
return mgr;
}
// ********** inner classes **********
/**
* Delegate sub-component-related behavior to this class.
*/
private class SubComponentReader {
/** the container that will hold the sub-components once they are read */
private ProjectSubFileComponentContainer container;
SubComponentReader(ProjectSubFileComponentContainer container) {
this.container = container;
}
void read() {
String ext = ProjectReader.this.defaultFileNameExtension();
// build the sub-directory that holds the sub-components
String subDirectoryName = ProjectReader.this.subDirectoryNameFor(this.container);
File subDirectory = new File(this.baseDirectory(), subDirectoryName);
// the sub-component names are set by TopLink when the project is read;
// and reset by ProjectWriter when the project is saved
Collection names = CollectionTools.set(this.container.originalProjectSubFileComponentNames());
// now use the sub-component names to read in the actual sub-components
Collection subComponents = new Vector(names.size());
for (Iterator stream = names.iterator(); stream.hasNext(); ) {
String name = (String) stream.next();
String fileName = FileTools.FILE_NAME_ENCODER.encode(name);
File subFile = new File(subDirectory, fileName + ext);
MWModel subComponent = (MWModel) this.readObject(subFile);
if (subComponent == null) {
ProjectReader.this.fireFileNotFound(subFile);
stream.remove(); // keep the list of names in synch with the files
} else {
subComponent.setParent((org.eclipse.persistence.tools.workbench.utility.node.Node) this.container);
subComponents.add(subComponent);
}
}
// and finally, put the sub-components into the container
this.container.setProjectSubFileComponents(subComponents);
}
private File baseDirectory() {
return ProjectReader.this.baseDirectory();
}
private Object readObject(File xmlFile) {
try {
return ProjectReader.this.readObject(xmlFile);
} catch (FileNotFoundException ex) {
return null;
} catch (IOException ex) {
throw new RuntimeException(ex);
}
}
}
}