/*
* Copyright (C) 2009 by Claas Wilke (claaswilke@gmx.net) This file is part of
* the Java Meta Model of Dresden OCL2 for Eclipse. Dresden OCL2 for Eclipse 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. Dresden OCL2 for Eclipse is distributed in the hope that it will be
* useful, but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser
* General Public License for more details. You should have received a copy of
* the GNU Lesser General Public License along with Dresden OCL2 for Eclipse. If
* not, see <http://www.gnu.org/licenses/>.
*/
package org.dresdenocl.metamodels.java.internal.provider;
import java.io.BufferedReader;
import java.io.File;
import java.io.FileNotFoundException;
import java.io.FileReader;
import java.io.IOException;
import java.net.MalformedURLException;
import java.net.URL;
import java.net.URLClassLoader;
import java.util.ArrayList;
import java.util.List;
import org.apache.log4j.Logger;
import org.eclipse.osgi.util.NLS;
import org.dresdenocl.metamodels.java.JavaMetaModelPlugin;
import org.dresdenocl.metamodels.java.internal.model.JavaModel;
import org.dresdenocl.metamodels.java.internal.msg.JavaMetaModelMessages;
import org.dresdenocl.model.IModel;
import org.dresdenocl.model.IModelProvider;
import org.dresdenocl.model.ModelAccessException;
import org.dresdenocl.model.ModelPlugin;
import org.dresdenocl.model.base.AbstractModelProvider;
import org.dresdenocl.modelbus.ModelBusPlugin;
/**
* <p>
* Implementation of the {@link IModelProvider} interface for Java {@link Class}
* files. This implementation will create an {@link JavaModel} instance.
* </p>
*
* @author Claas Wilke
*/
public class JavaModelProvider extends AbstractModelProvider implements
IModelProvider {
/** The {@link Logger} for this class. */
protected static final Logger LOGGER = JavaMetaModelPlugin
.getLogger(JavaModelProvider.class);
/*
* (non-Javadoc)
*
* @see org.dresdenocl.modelbus.IModelProvider#getModel(java.net.URL)
*/
public IModel getModel(URL modelURL) throws ModelAccessException {
/* Probably debug the entry of this method. */
if (LOGGER.isDebugEnabled()) {
LOGGER.debug("getModel(modelURL=" + modelURL + ") - enter");
}
// no else.
File modelFile;
String modelFilePath;
modelFilePath = modelURL.getFile();
/* Replace probably existing white spaces in the path. */
modelFilePath = modelFilePath.replaceAll("%20", " ");
modelFile = new File(modelFilePath);
/* Check if the given file does exists. */
if (!modelFile.exists()) {
String msg;
msg = NLS.bind(
JavaMetaModelMessages.JavaMetaModel_FileDoesNotExist,
modelURL);
throw new ModelAccessException(msg);
}
// no else.
IModel result;
result = null;
/* Probably handle the URL as a class file. */
if (modelFile.toString().endsWith(".class")) {
Class<?> modelClass;
modelClass = loadClassFromUrl(modelURL, new ArrayList<URL>());
result = this.getModel(modelClass);
}
/* Else handle the file as a configuration. */
else if (modelFile.toString().endsWith(".javamodel")) {
Class<?> modelClass;
modelClass = loadClassWithJarsFromUrl(modelFile);
result = this.getModel(modelClass);
}
else {
throw new ModelAccessException(
"Invalid kind of file. Java Meta-Model can only handle .class and .javamodel Files.");
}
/* Check if a Model has been loaded. */
if (result == null) {
String msg;
msg = NLS.bind(JavaMetaModelMessages.JavaMetaModel_ClassNotFound,
modelURL);
throw new ModelAccessException(msg);
}
// no else.
/* Probably debug the exit of this method. */
if (LOGGER.isDebugEnabled()) {
LOGGER.debug("getModel() - exit - return value=" + result); //$NON-NLS-1$
}
// no else.
return result;
}
/**
* <p>
* Returns the {@link IModel} which is described by a given {@link Class}.
* </p>
*
* @param modelClass
* The {@link Class} which shall be loaded as {@link IModel}.
* @return The {@link IModel} created from the given {@link Class}.
* @throws ModelAccessException
* Thrown, if an error during {@link IModel} loading occurs.
*/
public IModel getModel(Class<?> modelClass) throws ModelAccessException {
/* Probably debug the entry of this method. */
if (LOGGER.isDebugEnabled()) {
String msg;
msg = "getModel(";
msg += "modelClass = " + modelClass;
msg += ") - enter";
LOGGER.debug(msg);
}
// no else.
IModel result;
result = new JavaModel(modelClass, ModelBusPlugin
.getMetamodelRegistry().getMetamodel(JavaMetaModelPlugin.ID));
/* Probably debug the exit of this method. */
if (LOGGER.isDebugEnabled()) {
LOGGER.debug("getModel() - exit - return value=" + result); //$NON-NLS-1$
}
// no else.
return result;
}
/**
* <p>
* A helper method that computes the URLs of the folders which are parents
* of a given {@link File}.
*
* @param modelUrl
* The {@link URL} for which the folder URL shall be computed.
* @return The computed Folder URLs.
*/
private URL[] computeFolderURLs(URL modelUrl) {
List<URL> resultList;
File folderFile;
resultList = new ArrayList<URL>();
String filePath;
/* Replace probably existing white spaces in the path. */
filePath = modelUrl.getFile();
filePath = filePath.replaceAll("%20", " ");
folderFile = new File(filePath).getParentFile();
while (folderFile != null) {
if (folderFile.exists()) {
try {
resultList.add(folderFile.toURI().toURL());
}
catch (MalformedURLException e) {
/* Do nothing. */
}
folderFile = folderFile.getParentFile();
}
else {
break;
}
}
// end while.
return resultList.toArray(new URL[0]);
}
/**
* <p>
* A helper method that computes all possible names of a {@link Class} which
* shall be loaded from a given {@link URL}.
* </p>
*
* @param aURL
* The {@link URL}
* @return The {@link List} of possible {@link Class} names.
*/
private List<String> computePossibleClassNames(URL aURL) {
List<String> result;
String urlString;
String[] packages;
String aClassName;
result = new ArrayList<String>();
urlString = aURL.toString();
if (urlString.endsWith(".class")) {
urlString = urlString.substring(0, urlString.length() - 6);
}
// no else.
/* Split the urlString into possible packages. */
packages = urlString.split("/");
aClassName = null;
/* Create all possible names (from the shortest to the longest). */
for (int index = packages.length - 1; index >= 0; index--) {
if (aClassName == null) {
aClassName = packages[index];
}
else {
aClassName = packages[index] + "." + aClassName;
}
result.add(aClassName);
}
return result;
}
/**
* <p>
* A helper method that tries to find a {@link Class} that is described by
* the given URL.
* </p>
*
* @param modelURL
* The {@link URL} that shall be loaded as a {@link Class}.
* @param jarUrls
* A {@link List} of {@link URL}s leading to additional JAR
* archives the given {@link Class} (by its {@link URL})
* references.
* @return The loaded {@link Class}.
* @throws ModelAccessException
* Thrown, if the given {@link File} cannot be accessed as Java
* {@link IModel}.
*/
private Class<?> loadClassFromUrl(URL modelURL, List<URL> jarUrls)
throws ModelAccessException {
String modelFilePath;
modelFilePath = modelURL.getFile();
/* Replace probably existing white spaces in the path. */
modelFilePath = modelFilePath.replaceAll("%20", " ");
if (!(new File(modelFilePath).exists())) {
throw new ModelAccessException("The given java class '" + modelURL
+ "' does not exists.");
}
// no else.
Class<?> result;
result = null;
URLClassLoader aClassLoader;
URL[] folderURLs;
List<String> possibleClassNames;
int index;
folderURLs = this.computeFolderURLs(modelURL);
/* Compute all possible class names. */
possibleClassNames = this.computePossibleClassNames(modelURL);
index = 0;
/* Iterate through the names and try to load the class. */
for (String aClassName : possibleClassNames) {
if (index >= folderURLs.length) {
break;
}
// no else.
try {
List<URL> urls;
urls = new ArrayList<URL>();
urls.add(folderURLs[index]);
urls.addAll(jarUrls);
/*
* The parent class loader from the model bus plug-in is
* required to find types from EMF Ecore like EObject.
*/
aClassLoader = new URLClassLoader(urls.toArray(new URL[0]),
ModelPlugin.class.getClassLoader());
result = Class.forName(aClassName, true, aClassLoader);
/* If no exception is thrown, return the class. */
break;
}
catch (ClassNotFoundException e) {
/* Do nothing, continue iteration. */
}
catch (NoClassDefFoundError e) {
/* Do nothing, continue iteration. */
}
catch (SecurityException e) {
/* Do nothing, continue iteration. */
/* Could happen if the root package is called 'java' or 'javax'. */
}
index++;
}
// end for.
return result;
}
/**
* <p>
* A helper method that reads a given {@link File} as a java {@link IModel}
* configuration.
* </p>
* <p>
* The configuration is a very simple text file. The first line contains the
* relative location of the java {@link IModel} main {@link Class}. The
* following lines can contain referenced JAR archives.
* </p>
*
* @param modelFile
* The {@link File} containing the {@link IModel} configuration.
* @return The loaded model main {@link Class}.
* @throws ModelAccessException
* Thrown, if the given {@link File} cannot be accessed as Java
* {@link IModel}.
*/
private Class<?> loadClassWithJarsFromUrl(File modelFile)
throws ModelAccessException {
Class<?> result;
result = null;
BufferedReader configFileReader = null;
try {
List<URL> urls;
urls = new ArrayList<URL>();
configFileReader = new BufferedReader(new FileReader(modelFile));
/* Read the expected code from file. */
while (configFileReader.ready()) {
String relativeName;
relativeName = configFileReader.readLine();
URL aURL;
aURL = new URL("file:" + modelFile.getParent() + "/"
+ relativeName);
urls.add(aURL);
}
// end while.
if (urls.size() == 0) {
throw new ModelAccessException(
"Invalid configuration: The Java Model Configuration File must contain at least one URL of a class file.");
}
// no else.
result = this.loadClassFromUrl(urls.remove(0), urls);
}
// end try.
catch (FileNotFoundException e) {
throw new ModelAccessException("The given file '" + modelFile
+ "' was not found.");
}
catch (IOException e) {
throw new ModelAccessException("The given file '" + modelFile
+ "' could not be opened.");
}
// end catch.
finally {
if (configFileReader != null) {
try {
configFileReader.close();
} catch (IOException e) {
throw new ModelAccessException("The given file '"
+ modelFile + "' could not be read.");
}
}
}
return result;
}
}