/*
* (c) Copyright 2010-2011 AgileBirds
*
* This file is part of OpenFlexo.
*
* OpenFlexo is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* OpenFlexo 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 General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with OpenFlexo. If not, see <http://www.gnu.org/licenses/>.
*
*/
package org.openflexo.foundation.rm.cg;
import java.io.File;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.Enumeration;
import java.util.StringTokenizer;
import java.util.Vector;
import java.util.logging.Level;
import java.util.logging.Logger;
import org.openflexo.foundation.FlexoException;
import org.openflexo.foundation.cg.CGFile;
import org.openflexo.foundation.cg.CGFile.FileContentEditor;
import org.openflexo.foundation.cg.GenerationRepository;
import org.openflexo.foundation.cg.dm.CGContentRegenerated;
import org.openflexo.foundation.cg.generator.IFlexoResourceGenerator;
import org.openflexo.foundation.cg.generator.IGenerationException;
import org.openflexo.foundation.cg.templates.CGTemplate;
import org.openflexo.foundation.rm.FlexoGeneratedResource;
import org.openflexo.foundation.rm.FlexoProject;
import org.openflexo.foundation.rm.FlexoProjectBuilder;
import org.openflexo.foundation.rm.FlexoResource;
import org.openflexo.foundation.rm.FlexoResourceData;
import org.openflexo.foundation.rm.FlexoResourceTree;
import org.openflexo.foundation.rm.GeneratedResourceData;
import org.openflexo.foundation.rm.InvalidFileNameException;
import org.openflexo.foundation.rm.LoadResourceException;
import org.openflexo.foundation.rm.ResourceDependencyLoopException;
import org.openflexo.foundation.rm.SaveResourceException;
import org.openflexo.foundation.utils.FlexoProjectFile;
import org.openflexo.foundation.utils.ProjectLoadingCancelledException;
/**
* @author sylvain
*
*/
public abstract class CGRepositoryFileResource<GRD extends GeneratedResourceData, G extends IFlexoResourceGenerator, F extends CGFile>
extends FlexoGeneratedResource<GRD> {
private static final Logger logger = Logger.getLogger(CGRepositoryFileResource.class.getPackage().getName());
private F _cgFile;
private String _name;
private boolean _forceRegenerate = false;
private Date _lastAcceptingDate;
private Date _lastGenerationCheckedDate;
private File _lastGeneratedFile;
private File _lastAcceptedFile;
private boolean generationChangedContent = false;
private G _generator;
// final only to ensure that it wasn't overrided somewhere
// but if there is a real need to override it : final modifier may be removed
final public G getGenerator() {
return _generator;
}
// final only to ensure that it wasn't overrided somewhere
// but if there is a real need to override it : final modifier may be removed
final public void setGenerator(G generator) {
if (_generator == generator) {
return;
}
Vector<CGTemplate> templates = null;
if (_generator != null) {
templates = _generator.getUsedTemplates();
_generator.removeFromGeneratedResourcesGeneratedByThisGenerator(this);
}
_generator = generator;
if (generator != null) {
generator.addToGeneratedResourcesGeneratedByThisGenerator(this);
if (getCGFile() != null) {
generator.addObserver(getCGFile());
if (templates == null) {
templates = new Vector<CGTemplate>();
}
if (templates.size() == 0) {
for (String templateRelativePath : getCGFile().getUsedTemplates()) {
CGTemplate file;
if (getCGFile().getRepository().getPreferredTemplateRepository() != null) {
file = getCGFile().getRepository().getPreferredTemplateRepository()
.getTemplateWithRelativePath(templateRelativePath);
} else {
file = getCGFile().getGeneratedCode().getTemplates().getApplicationRepository()
.getTemplateWithRelativePath(templateRelativePath);
}
if (file != null) {
templates.add(file);
}
}
}
generator.setUsedTemplates(templates);
if (generator.getTemplateLocator() != null) {
addToDependentResources(generator.getTemplateLocator());
}
}
}
}
@Override
public synchronized void delete(boolean deleteFile) {
if (getGenerator() != null && getCGFile() != null) {
getGenerator().deleteObserver(getCGFile());
}
super.delete(deleteFile);
}
@Override
public String getName() {
return _name;
}
@Override
public void setName(String aName) {
_name = aName;
}
/**
* @param builder
*/
public CGRepositoryFileResource(FlexoProjectBuilder builder) {
super(builder);
}
/**
* @param aProject
*/
public CGRepositoryFileResource(FlexoProject aProject) {
super(aProject);
}
public GenerationRepository getRepository() {
if (getCGFile() != null) {
return getCGFile().getRepository();
}
return null;
}
public final F getCGFile() {
return _cgFile;
}
public final void setCGFile(F cgFile) {
if (getCGFile() != cgFile && cgFile != null && getGenerator() != null) {
getGenerator().addObserver(cgFile);
}
_cgFile = cgFile;
}
public FlexoProjectFile makeFlexoProjectFile(String relativePath, String name) {
if (relativePath == null) {
relativePath = "";
}
StringBuffer sb = new StringBuffer();
sb.append(getCGFile().getSymbolicDirectory().getDirectory().getRelativePath());
addPathFrom(sb, extractSignificantPathFrom(sb.toString(), relativePath));
addPathFrom(sb, name);
String pathFromRepository = sb.toString();
return new FlexoProjectFile(getProject(), getCGFile().getRepository().getSourceCodeRepository(), pathFromRepository);
}
private void addPathFrom(StringBuffer result, String aPath) {
aPath = aPath.replace('\\', '/');
StringTokenizer st = new StringTokenizer(aPath, "/");
while (st.hasMoreElements()) {
String next = st.nextToken();
if (result.toString().endsWith("/")) {
result.append(next);
} else {
result.append("/" + next);
}
}
}
private String extractSignificantPathFrom(String previousPath, String aPath) {
previousPath = previousPath.replace('\\', '/');
aPath = aPath.replace('\\', '/');
Vector<String> path1 = new Vector<String>();
StringTokenizer st = new StringTokenizer(previousPath, "/");
while (st.hasMoreElements()) {
path1.add(st.nextToken());
}
Vector<String> path2 = new Vector<String>();
st = new StringTokenizer(aPath, "/");
while (st.hasMoreElements()) {
path2.add(st.nextToken());
}
int removeThat = 0;
while (removeThat < path1.size() && removeThat < path2.size() && path1.get(removeThat).equals(path2.get(removeThat))) {
removeThat++;
}
StringBuffer returned = new StringBuffer();
boolean isFirst = true;
for (int i = removeThat; i < path2.size(); i++) {
returned.append((!isFirst ? "/" : "") + path2.get(i));
isFirst = false;
}
return returned.toString();
}
public Date getLastAcceptingDate() {
if (_lastAcceptingDate == null || getLastGenerationDate().getTime() > _lastAcceptingDate.getTime()) {
_lastAcceptingDate = getLastGenerationDate();
}
return _lastAcceptingDate;
}
public void setLastAcceptingDate(Date aDate) {
_lastAcceptingDate = aDate;
}
public Date getLastGenerationCheckedDate() {
if (_lastGenerationCheckedDate == null || getLastGenerationDate().getTime() > _lastGenerationCheckedDate.getTime() || isConnected()
&& !getFile().exists()) {
_lastGenerationCheckedDate = getLastGenerationDate();
}
return _lastGenerationCheckedDate;
}
public void setLastGenerationCheckedDate(Date aDate) {
_lastGenerationCheckedDate = aDate;
}
public Date getMemoryLastGenerationDate() {
IFlexoResourceGenerator generator = getGenerator();
if (generator != null) {
return generator.getMemoryLastGenerationDate();
}
return null;
}
/**
* This date is VERY IMPORTANT and CRITICAL since this is the date used by ResourceManager to compute dependancies between resources.
* This method returns the date that must be considered as last known update for this resource
*
* The date to be considered for generated resources are typically the date when this resource was generated for the last time BUT here,
* we override this basic scheme by maintaining an other date which correspond to the date when this resource has been checked and
* declared unchanged compared to the version on the disk (see DismissUnchangedGeneratedFiles).
*
* @return a Date object
*/
@Override
public final Date getLastUpdate() {
if (_memoryUpdateComputation) {
return getMemoryLastGenerationDate();
}
return getLastGenerationCheckedDate();
}
private GenerationStatus retrieveGenerationStatus() {
boolean diskUpdate = getDiskLastModifiedDate().getTime() > getLastAcceptingDate().getTime() + ACCEPTABLE_FS_DELAY;
if (logger.isLoggable(Level.FINE) && diskUpdate) {
logger.fine("fileName=" + getFileName());
logger.fine("getLastGenerationDate()[" + new SimpleDateFormat("dd/MM HH:mm:ss SSS").format(getLastGenerationDate()) + "]");
logger.fine("getLastAcceptingDate()[" + new SimpleDateFormat("dd/MM HH:mm:ss SSS").format(getLastAcceptingDate()) + "]"
+ " < getDiskLastModifiedDate()[" + new SimpleDateFormat("dd/MM HH:mm:ss SSS").format(getDiskLastModifiedDate()) + "]");
}
if (getGenerator() != null) {
if (hasGenerationError()) {
return GenerationStatus.GenerationError;
} else if (getGenerator().getGeneratedCode() == null) {
return GenerationStatus.NotGenerated;
}
}
if (!(this instanceof GenerationAvailableFileResourceInterface)) {
return GenerationStatus.CodeGenerationNotAvailable;
} else if (getCGFile() == null || getFile() == null) {
return GenerationStatus.Unknown;
} else if (getCGFile().isMarkedForDeletion()) {
return GenerationStatus.GenerationRemoved;
} else if (!isCodeGenerationAvailable()) {
return GenerationStatus.CodeGenerationNotSynchronized;
} else if (getCGFile().isOverrideScheduled()) {
return GenerationStatus.OverrideScheduled;
} else if (!getFile().exists() || needsGeneration()) {
if (diskUpdate && getProject() != null && getProject().isComputeDiff()) {
return getCGFile().isMarkedAsMerged() ? GenerationStatus.ConflictingMarkedAsMerged : GenerationStatus.ConflictingUnMerged;
} else {
if (!getFile().exists()) {
return GenerationStatus.GenerationAdded;
} else {
if (getProject() != null && getProject().isComputeDiff() && getCGFile().isGenerationConflicting()) {
return getCGFile().isMarkedAsMerged() ? GenerationStatus.ConflictingMarkedAsMerged
: GenerationStatus.ConflictingUnMerged;
} else {
return GenerationStatus.GenerationModified;
}
}
}
} else {
if (diskUpdate) {
if (getFile().exists()) {
return GenerationStatus.DiskModified;
} else {
return GenerationStatus.DiskRemoved;
}
}
return GenerationStatus.UpToDate;
}
}
private GenerationStatus previousGenerationStatus;
public final GenerationStatus getGenerationStatus() {
GenerationStatus returnedStatus = retrieveGenerationStatus();
if (returnedStatus != previousGenerationStatus) {
if (logger.isLoggable(Level.FINE)) {
logger.fine("File " + getFileName() + " changed status from " + previousGenerationStatus + " to " + returnedStatus);
}
GenerationStatus old = previousGenerationStatus;
previousGenerationStatus = returnedStatus;
if (getCGFile() != null) {
getCGFile().notifyGenerationStatusChange(old, returnedStatus);
if (getCGFile().getRepository() != null) {
getCGFile().getRepository().refresh();
}
}
}
return returnedStatus;
}
/**
* Overrides addToDependantResources
*
* @see org.openflexo.foundation.rm.FlexoResource#addToDependentResources(org.openflexo.foundation.rm.FlexoResource)
*/
/* @Override
public void addToDependantResources(FlexoResource aDependantResource)
{
super.addToDependantResources(aDependantResource);
//getGenerationStatus();
}*/
private String _resourceClassName;
public String _getResourceClassName() {
if (_resourceClassName != null) {
return _resourceClassName;
} else {
return this.getClass().getName();
}
}
public void _setResourceClassName(String aClassName) {
_resourceClassName = aClassName;
}
public File getLastGeneratedFile() {
if (_lastGeneratedFile == null && getCGFile() != null) {
_lastGeneratedFile = new File(getCGFile().getRepository().getCodeGenerationWorkingDirectory(), getResourceFile()
.getRelativePath() + ".LAST_GENERATED");
if (logger.isLoggable(Level.FINE)) {
logger.fine("_lastGeneratedFile" + _lastGeneratedFile.getAbsolutePath());
}
}
return _lastGeneratedFile;
}
public File getLastAcceptedFile() {
if (_lastAcceptedFile == null && getCGFile() != null) {
_lastAcceptedFile = new File(getCGFile().getRepository().getCodeGenerationWorkingDirectory(), getResourceFile()
.getRelativePath() + ".LAST_ACCEPTED");
if (logger.isLoggable(Level.FINE)) {
logger.fine("_lastAcceptedFile" + _lastAcceptedFile.getAbsolutePath());
}
}
return _lastAcceptedFile;
}
@Override
public final boolean needsGeneration() {
// logger.info("File: "+getFileName()+" super.needsGeneration()="+super.needsGeneration());
// logger.info("File: "+getFileName()+" generator="+(getGenerator() != null &&
// getGenerator().needsRegenerationBecauseOfTemplateUpdated(getLastUpdate())));
// logger.info("File: "+getFileName()+" _forceRegenerate="+_forceRegenerate);
// Take care that because of template updating, memory generation can be up-to-date
// while disk writing is still required
boolean returned = generationChangedContent || super.needsGeneration() || getGenerator() != null
&& getGenerator().needsRegenerationBecauseOfTemplateUpdated(getLastUpdate()) || _forceRegenerate;
// We do this here, otherwise retrieveNeedsMemoryGenerationFlag may override this
// needsUpdateReason = super.getNeedsUpdateReason();
return returned;
}
private boolean previousNeedsMemoryGeneration = false;
/**
* Returns a flag indicating if generation in memory needs to be re-run. Returns true if generator is not null and if result coded by
* the generator is not up-to-date and therefore generator must be re-run. To compute this, use the RM.
*
* @return
*/
public boolean needsMemoryGeneration() {
if (getGenerator() == null) {
return false;
}
boolean returnedFlag = getGenerator().needsGeneration();
if (returnedFlag != previousNeedsMemoryGeneration) {
if (logger.isLoggable(Level.FINE)) {
logger.fine("File " + getFileName() + " changed needsMemoryGeneration status");
}
previousNeedsMemoryGeneration = returnedFlag;
if (getCGFile() != null) {
getCGFile().notifyGenerationStatusChange(null, null);
getCGFile().getRepository().refresh();
}
}
return returnedFlag;
}
/**
* Returns a flag indicating if generation in memory needs to be re-run. Returns true if generator is not null and if result coded by
* the generator is not up-to-date and therefore generator must be re-run. To compute this, use the RM.
*
* @return
*/
public boolean retrieveNeedsMemoryGenerationFlag() {
if (getGenerator() == null) {
return false;
}
try {
IFlexoResourceGenerator generator = getGenerator();
/*if (generator.needsGeneration()) return true;*/
Date lastTimeItWasGenerated = generator.getMemoryLastGenerationDate();
// OK, from this point are some explanations required:
// Since we use the same dependancies here to perform 2 computations (needsUpdate - on disk - and
// needsUpdate - on memory - we have here to change initial request date, and we must use
// the last time it was generated given by the generator itself.
// But this is not enough because internal RM scheme also uses
// getLastSynchronizedWithResource(FlexoResource) method (backward synchronization scheme).
// So we also must override this method by returning lastTimeItWasGenerated
// in the special case of "memory-needs-update" computation
// Hope you understand what i mean..
// 07/12/2006 / Sylvain
getDependentResources().update(); // Clears the dependancy cache
_memoryUpdateComputation = true;
boolean returned = needsUpdate();
_memoryUpdateComputation = false;
getDependentResources().update();// Clears the dependancy cache
if (logger.isLoggable(Level.FINE)) {
logger.fine("Resource " + getFileName() + " lastTimeItWasGenerated="
+ new SimpleDateFormat("dd/MM HH:mm:ss SSS").format(lastTimeItWasGenerated) + " returns " + returned + " reason "
+ getNeedsUpdateReason());
}
return returned;
} catch (ResourceDependencyLoopException e) {
if (logger.isLoggable(Level.SEVERE)) {
logger.log(Level.SEVERE, "Loop found in dependant resources of " + this + "!", e);
}
return false;
}
}
private boolean _memoryUpdateComputation = false;
public final boolean isCodeGenerationAvailable() {
return getGenerator() != null;
}
@Override
public boolean ensureGenerationIsUpToDate() throws FlexoException {
if (getGenerationException() == null) {
getDependentResources().update();
return super.ensureGenerationIsUpToDate();
}
return false;
}
public final IGenerationException getGenerationException() {
if (isCodeGenerationAvailable()) {
return getGenerator().getGenerationException();
}
return null;
}
public boolean hasGenerationError() {
return getGenerationException() != null;
}
public abstract void saveEditedVersion(FileContentEditor editor) throws SaveResourceException;
public void notifyRegenerated(CGContentRegenerated notification) {
if (getGeneratedResourceData() != null && getGeneratedResourceData() instanceof AbstractGeneratedFile) {
((AbstractGeneratedFile) getGeneratedResourceData()).notifyRegenerated(notification);
generationChangedContent = !((AbstractGeneratedFile) getGeneratedResourceData()).doesGenerationKeepFileUnchanged();
}
}
public void notifyResourceDismissal() {
generationChangedContent = false;
}
public void notifyResourceHasBeenWritten() {
generationChangedContent = false;
}
public void notifyResourceChangedOnDisk() {
if (isConnected()) {
if (getGeneratedResourceData() != null && getGeneratedResourceData() instanceof AbstractGeneratedFile) {
((AbstractGeneratedFile) getGeneratedResourceData()).notifyVersionChangedOnDisk();
}
if (getCGFile() != null) {
getCGFile().notifyResourceChangedOnDisk();
}
}
}
public boolean isForceRegenerate() {
return _forceRegenerate;
}
public void setForceRegenerate(boolean forceRegenerate) {
_forceRegenerate = forceRegenerate;
}
/**
* This method is intended to be overidden by sub-classes that need to free resources and data.
*
*/
@Override
public void finalizeGeneration() {
super.finalizeGeneration();
_forceRegenerate = false;
}
public void acceptDiskVersion() throws SaveGeneratedResourceIOException {
if (getGeneratedResourceData() != null && getGeneratedResourceData() instanceof AbstractGeneratedFile) {
setLastAcceptingDate(new Date());
((AbstractGeneratedFile) getGeneratedResourceData()).acceptDiskVersion();
}
}
public boolean doesGenerationKeepFileUnchanged() {
if (getGeneratedResourceData() != null
&& getGeneratedResourceData() instanceof AbstractGeneratedFile
&& (getGenerationStatus() == GenerationStatus.GenerationModified || getGenerationStatus() == GenerationStatus.OverrideScheduled)) {
return ((AbstractGeneratedFile) getGeneratedResourceData()).doesGenerationKeepFileUnchanged();
}
// logger.info("getGeneratedResourceData()="+getGeneratedResourceData());
// logger.info("getGenerationStatus()="+getGenerationStatus());
return false;
}
/* private String needsUpdateReason;
/**
* debug
* @return
*/
/* @Override
public String getNeedsUpdateReason()
{
return needsUpdateReason;
}
*/
/**
* Overrides renameFileTo
*
* @throws IOException
*
* @see org.openflexo.foundation.rm.FlexoFileResource#renameFileTo(java.lang.String)
*/
@Override
public boolean renameFileTo(String name) throws InvalidFileNameException, IOException {
String old = getFileName();
boolean succeed = super.renameFileTo(name);
if (succeed && getCGFile() != null) {
getCGFile().notifyFileNameChanged(old, name);
}
return succeed;
}
@Override
protected void performUpdating(FlexoResourceTree updatedResources) throws ResourceDependencyLoopException, FlexoException,
FileNotFoundException {
if (getGenerator() != null && (needsMemoryGeneration() || isForceRegenerate())) {
getGenerator().generate(isForceRegenerate());
}
super.performUpdating(updatedResources);
}
/**
*
*/
public void getDependantResourcesUpToDate() {
try {
FlexoResourceTree updatedResources = performUpdateDependanciesModel(new Vector<FlexoResource<FlexoResourceData>>());
if (!updatedResources.isEmpty()) {
for (Enumeration<FlexoResource<FlexoResourceData>> e = getDependentResources().elements(false,
getProject().getDependancyScheme()); e.hasMoreElements();) {
FlexoResource<FlexoResourceData> resource = e.nextElement();
resource.update();
}
}
} catch (LoadResourceException e) {
if (logger.isLoggable(Level.WARNING)) {
logger.log(Level.WARNING, "Load resource exception.", e);
}
} catch (FileNotFoundException e) {
if (logger.isLoggable(Level.WARNING)) {
logger.log(Level.WARNING, "File not found exception.", e);
}
} catch (ProjectLoadingCancelledException e) {
if (logger.isLoggable(Level.WARNING)) {
logger.log(Level.WARNING, "Project loading cancel exception.", e);
}
} catch (ResourceDependencyLoopException e) {
if (logger.isLoggable(Level.WARNING)) {
logger.log(Level.WARNING, "Loop in dependancies exception.", e);
}
} catch (FlexoException e) {
if (logger.isLoggable(Level.WARNING)) {
logger.log(Level.WARNING, "Flexo exception.", e);
}
}
}
private boolean isNotifiedDependantResourceChange = false;
@Override
public void notifyDependantResourceChange(FlexoResource origin) {
if (!isNotifiedDependantResourceChange && isActive()) {
isNotifiedDependantResourceChange = true;
super.notifyDependantResourceChange(origin);
if (isConnected()) {
if (getCGFile() != null && getGenerator() != null) {
getGenerationStatus();
}
}
isNotifiedDependantResourceChange = false;
}
}
}