package org.archstudio.dblgen.builder; import java.io.ByteArrayInputStream; import java.io.FileNotFoundException; import java.io.IOException; import java.io.InputStream; import java.text.DateFormat; import java.text.SimpleDateFormat; import java.util.HashMap; import java.util.List; import java.util.Map; import org.archstudio.dblgen.Xadl3SchemaNature; import org.archstudio.dblgen.builder.Xadl3SchemaLocation.UpdateFrequency; import org.archstudio.sysutils.SystemUtils; import org.eclipse.core.resources.IFile; import org.eclipse.core.resources.IFolder; import org.eclipse.core.resources.IProject; import org.eclipse.core.runtime.CoreException; import org.eclipse.core.runtime.QualifiedName; import org.eclipse.emf.common.util.URI; import org.eclipse.emf.ecore.resource.ResourceSet; import org.eclipse.emf.ecore.resource.URIConverter; import org.eclipse.emf.ecore.resource.impl.ResourceSetImpl; import com.google.common.base.Function; import com.google.common.base.Predicate; import com.google.common.collect.Iterables; import com.google.common.collect.Lists; /* * This class makes a non-standard use of Eclipse extension points. This class lives at the meta-Eclipse level, which * means it's part of a plug-in that's installed in the development copy of Eclipse. ArchStudio runs in the next * instance. So, here we want to look at some data defined in plug-in extension points. Normally, that data lives in * plug-ins installed in the current instance of Eclipse (i.e., that were developed one meta-level higher). But in this * case, we want to look at data that's stored in the plug-ins whose code is in the workspace. */ public class Xadl3SchemaUpdater { private static final Xadl3SchemaUpdater INSTANCE = new Xadl3SchemaUpdater(); public static Xadl3SchemaUpdater getInstance() { return INSTANCE; } private final ResourceSet resourceSet; private final URIConverter uriConverter; private Xadl3SchemaUpdater() { resourceSet = new ResourceSetImpl(); uriConverter = resourceSet.getURIConverter(); } public boolean hasXadl3Nature(IProject project) throws CoreException { return project.getNature(Xadl3SchemaNature.NATURE_ID) != null; } public URI getSchemaURI(String urlString) { return URI.createURI(urlString); } public String getContents(InputStream is) throws IOException { try { return new String(SystemUtils.blt(is)); } finally { SystemUtils.closeQuietly(is); } } public String getContents(URI uri) throws IOException { return getContents(uriConverter.createInputStream(uri)); } public boolean schemaExists(IProject project, String schemaFileName) { IFolder modelFolder = project.getFolder("model"); if (modelFolder.exists()) { IFile schemaFile = modelFolder.getFile(schemaFileName); if (schemaFile != null && schemaFile.exists()) { return true; } } return false; } public String getSchemaContents(IProject project, String schemaFileName) throws IOException, CoreException { IFolder modelFolder = project.getFolder("model"); if (modelFolder.exists()) { IFile schemaFile = modelFolder.getFile(schemaFileName); if (schemaFile != null && schemaFile.exists()) { return getContents(schemaFile.getContents()); } } throw new FileNotFoundException(schemaFileName); } /* * Last-updated times for schemas are stored as persistent project properties. The qualified name has two parts: the * qualifier is a distinguished URI and the local name is the filename of the schema. The property value is a * stringified Long representing the last updated time for the schema. */ private final static String SCHEMA_LAST_UPDATE_TIME_URI = "http://www.archstudio.org/schemaLastUpdateTime/"; public Map<String, Long> getSchemaLastUpdatedTimes(IProject project) { Map<String, Long> lastUpdatedTimes = new HashMap<String, Long>(); try { for (Object element : project.getPersistentProperties().keySet()) { QualifiedName qn = (QualifiedName) element; String qualifier = qn.getQualifier(); if (qualifier != null && qualifier.equals(SCHEMA_LAST_UPDATE_TIME_URI)) { String schemaName = qn.getLocalName(); if (schemaName != null) { String lastUpdateTimeString = project.getPersistentProperty(qn); try { lastUpdatedTimes.put(schemaName, new Long(lastUpdateTimeString)); } catch (NumberFormatException nfe) { } } } } } catch (CoreException ce) { ce.printStackTrace(); } return lastUpdatedTimes; } public long getSchemaLastUpdateTime(IProject project, String schemaFileName) { QualifiedName qn = new QualifiedName(SCHEMA_LAST_UPDATE_TIME_URI, schemaFileName); try { return Long.valueOf(project.getPersistentProperty(qn)); } catch (CoreException ce) { return -1; } catch (NumberFormatException nfe) { return -1; } } public void setSchemaLastUpdateTime(IProject project, String schemaFileName, long time) { QualifiedName qn = new QualifiedName(SCHEMA_LAST_UPDATE_TIME_URI, schemaFileName); try { project.setPersistentProperty(qn, Long.toString(time)); } catch (CoreException ce) { // This is best-effort ce.printStackTrace(); } } public void updateSchemasIfNecessary(IProject project) throws IOException, CoreException { for (Xadl3SchemaLocation schemaLocation : Xadl3SchemaLocation.parse(project)) { updateSchemaIfNecessary(project, schemaLocation); } } private boolean updateTimePassed(IProject project, long lastUpdateTime, Xadl3SchemaLocation schemaLocation) throws CoreException { if (schemaLocation.getAutoUpdateFrequency().equals(UpdateFrequency.NEVER)) { // however, if the src folder is missing or empty, then do a build IFolder srcFolder = project.getFolder("src"); if (!srcFolder.exists() || srcFolder.members().length == 0) { return true; } return false; } else { if (lastUpdateTime == -1) { // We've never updated this schema before, let's do it. return true; } // See if the difference between the current time and the last // update time is greater than the update frequency. long currentTime = new java.util.Date().getTime(); if (currentTime - lastUpdateTime > schemaLocation.getAutoUpdateFrequency().getNumMilliseconds()) { return true; } return false; } } private static final DateFormat df = new SimpleDateFormat("yyyy_MM_dd_HHmmss"); private String getTimestampForBackupFile() { return df.format(new java.util.Date()); } private boolean writeSchema(IProject project, String schemaFileName, String newContents, boolean backupOldFile) { try { IFolder modelFolder = project.getFolder("model"); if (!modelFolder.exists()) { modelFolder.create(true, true, null); } if (modelFolder.exists()) { IFile schemaFile = modelFolder.getFile(schemaFileName); if (schemaFile.exists() && backupOldFile) { // Let's be nice and back up the old file. IFile backupFile = modelFolder.getFile(schemaFileName + "_" + getTimestampForBackupFile()); InputStream is = schemaFile.getContents(); if (!backupFile.exists()) { backupFile.create(is, true, null); } else { backupFile.setContents(is, true, true, null); } SystemUtils.closeQuietly(is); } if (!schemaFile.exists()) { schemaFile.create(new ByteArrayInputStream(newContents.getBytes()), true, null); } else { schemaFile.setContents(new ByteArrayInputStream(newContents.getBytes()), true, true, null); } return true; } } catch (CoreException ce) { ce.printStackTrace(); } return false; } private void updateSchemaIfNecessary(IProject project, Xadl3SchemaLocation schemaLocation) throws IOException, CoreException { if (schemaLocation.isCopyLocally()) { URI uri = schemaLocation.getUrl(); String schemaFileName = uri.path().substring(uri.path().lastIndexOf('/') + 1); // See if the schema already exists. If so, we may not have to update it. String newSchemaContents = null; boolean needsUpdate = false; if (schemaExists(project, schemaFileName)) { // Check when we last updated it. long lastUpdateTime = getSchemaLastUpdateTime(project, schemaFileName); if (updateTimePassed(project, lastUpdateTime, schemaLocation)) { // OK, it's time to check for an update. Let's see if the schema's contents have changed. newSchemaContents = getContents(uri); if (newSchemaContents != null) { // Only process if we successfully read the contents String oldSchemaContents = getSchemaContents(project, schemaFileName); if (newSchemaContents.equals(oldSchemaContents)) { // The contents were equivalent; this counts as a done update. needsUpdate = false; setSchemaLastUpdateTime(project, schemaFileName, new java.util.Date().getTime()); } else { // We have to update. needsUpdate = true; } } } } else { // If schema does not exist locally, then it definitely needs an update. needsUpdate = true; newSchemaContents = getContents(uri); } if (needsUpdate && newSchemaContents != null) { boolean success = writeSchema(project, schemaFileName, newSchemaContents, true); if (success) { setSchemaLastUpdateTime(project, schemaFileName, new java.util.Date().getTime()); } } } } public List<String> getNonCopiedSchemaURIs(IProject project) { return Lists.newArrayList(Iterables.transform( Iterables.filter(Xadl3SchemaLocation.parse(project), new Predicate<Xadl3SchemaLocation>() { @Override public boolean apply(Xadl3SchemaLocation input) { return !input.isCopyLocally(); } }), new Function<Xadl3SchemaLocation, String>() { @Override public String apply(Xadl3SchemaLocation input) { return input.getUrl().toString(); } })); } }