/*
* (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.IOException;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.Observable;
import java.util.Observer;
import java.util.logging.Level;
import java.util.logging.Logger;
import org.openflexo.diff.DiffSource;
import org.openflexo.diff.merge.DefaultMergedDocumentType;
import org.openflexo.diff.merge.Merge;
import org.openflexo.diff.merge.MergedDocumentType;
import org.openflexo.foundation.FlexoException;
import org.openflexo.foundation.IOFlexoException;
import org.openflexo.foundation.action.NotImplementedException;
import org.openflexo.foundation.cg.dm.CGContentRegenerated;
import org.openflexo.foundation.cg.version.CGVersionIdentifier;
import org.openflexo.foundation.rm.FlexoFileResource.FileWritingLock;
import org.openflexo.foundation.rm.cg.ContentSource.ContentSourceType;
import org.openflexo.localization.FlexoLocalization;
import org.openflexo.toolbox.FileUtils;
public abstract class ASCIIFile extends AbstractGeneratedFile {
static final Logger logger = Logger.getLogger(ASCIIFile.class.getPackage().getName());
private DiffSource currentDiskContent = null;
private DiffSource lastGeneratedContent = null;
private DiffSource lastAcceptedContent = null;
private Merge _generationMerge;
private ResultFileMerge _resultFileMerge;
private boolean hasDiskVersion = false;
/**
*
*/
public ASCIIFile(File f) {
super(f);
}
/**
*
*/
public ASCIIFile() {
super();
}
/**
* Returns flag indicating if merge for generation is actually raising conflicts (collision in changes)
*/
@Override
public boolean isGenerationConflicting() {
if (getGenerationMerge() == null) {
return false;
}
return getGenerationMerge().isReallyConflicting();
}
/**
* @throws SaveGeneratedResourceIOException
* @throws SaveGeneratedResourceException
* @throws UnresolvedConflictException
*/
@Override
public void writeToFile(File aFile) throws SaveGeneratedResourceIOException, SaveGeneratedResourceException,
UnresolvedConflictException {
if (logger.isLoggable(Level.FINE)) {
logger.fine("***** writeToFile() called in " + getFlexoResource().getFileName() + " file " + aFile.getAbsolutePath() + " on "
+ new SimpleDateFormat("dd/MM HH:mm:ss SSS").format(new Date()));
}
File path = aFile.getParentFile();
// Creates directory when non existant
if (!path.exists()) {
path.mkdirs();
}
// Save content
try {
// If no content available, throw exception
if (getContentToWriteOnDisk() == null) {
logger.warning("getContentToWriteOnDisk() is null ! for "
+ (getFlexoResource() != null ? getFlexoResource().getFullyQualifiedName() : getFile()));
throw new SaveGeneratedResourceException(getFlexoResource(), "Cannot access content to write on disk: "
+ (getFlexoResource() != null ? getFlexoResource().getFullyQualifiedName() : getFile()));
}
// If current generation is conflicting and not marked as merged, don't do it and throw exception
if (hasDiskVersion && getFlexoResource().getGenerationStatus() == GenerationStatus.ConflictingUnMerged
&& !isOverrideScheduled()) {
throw new UnresolvedConflictException(getFlexoResource());
}
boolean needsNotifyEndOfSaving = false;
FileWritingLock lock = null;
if (!getFlexoResource().isSaving()) {
logger.warning("writeToFile() called in " + getFlexoResource().getFileName() + " outside of RM-saving scheme");
lock = getFlexoResource().willWriteOnDisk();
needsNotifyEndOfSaving = true;
}
// Save file in history if edited since last generation
if (fileOnDiskHasBeenEdited() && manageHistory()) {
getHistory().storeCurrentFileInHistory(CGVersionIdentifier.VersionType.DiskUpdate);
}
if (logger.isLoggable(Level.FINEST)) {
logger.finest("Really writing " + aFile.getAbsolutePath() + " to disk");
}
// Save to file the new generation
FileUtils.saveToFile(aFile, getContentToWriteOnDisk(), getEncoding());
if (!aFile.exists()) {
throw new SaveGeneratedResourceIOException(getFlexoResource(), null);
}
if (needsNotifyEndOfSaving) {
getFlexoResource().hasWrittenOnDisk(lock);
}
if (getProject() != null && getProject().isComputeDiff()) {
currentDiskContent = new DiffSource(getContentToWriteOnDisk());
lastAcceptedContent = new DiffSource(getContentToWriteOnDisk());
}
// Save file in history
if (manageHistory()) {
getHistory().storeCurrentFileInHistory(CGVersionIdentifier.VersionType.GenerationIteration);
}
// Now save last generated
saveLastGeneratedFile();
// Now save last accepted
saveLastAcceptedFile();
// If this was an overriding, discard it
_overrideIsScheduled = false;
if (getProject() != null && getProject().isComputeDiff()) {
rebuildMerges();
}
hasDiskVersion = true;
}
catch (IOException e) {
throw new SaveGeneratedResourceIOException(getFlexoResource(), e);
}
}
private void saveLastGeneratedFile() throws IOException {
if (logger.isLoggable(Level.FINE)) {
logger.fine("Try to save " + getLastGeneratedFile().getAbsolutePath());
}
if (!getLastGeneratedFile().getParentFile().exists()) {
getLastGeneratedFile().getParentFile().mkdirs();
}
if (getCurrentGeneration() != null) {
try {
FileUtils.saveToFile(getLastGeneratedFile(), getCurrentGeneration(), getEncoding());
if (getProject() != null && getProject().isComputeDiff()) {
lastGeneratedContent = new DiffSource(getCurrentGeneration());
}
} catch (IOException e) {
logger.warning("IOException occured when trying to write " + getLastGeneratedFile().getAbsolutePath() + "parent="
+ getLastGeneratedFile().getParentFile());
throw e;
}
} else {
lastGeneratedContent = new DiffSource(FlexoLocalization.localizedForKey("unable_to_access_last_generated_file") + "\n"
+ FlexoLocalization.localizedForKey("file") + " : " + getLastGeneratedFile().getAbsolutePath());
}
}
public void saveAsLastGenerated(String newContent) throws IOException {
if (logger.isLoggable(Level.FINE)) {
logger.fine("Try to save " + getLastGeneratedFile().getAbsolutePath());
}
if (!getLastGeneratedFile().getParentFile().exists()) {
getLastGeneratedFile().getParentFile().mkdirs();
}
try {
FileUtils.saveToFile(getLastGeneratedFile(), newContent, getEncoding());
lastGeneratedContent = new DiffSource(newContent);
} catch (IOException e) {
logger.warning("IOException occured when trying to write " + getLastGeneratedFile().getAbsolutePath() + "parent="
+ getLastGeneratedFile().getParentFile());
throw e;
}
}
private void saveLastAcceptedFile() throws IOException {
if (logger.isLoggable(Level.FINE)) {
logger.fine("Try to save " + getLastAcceptedFile().getAbsolutePath());
}
if (!getLastAcceptedFile().getParentFile().exists()) {
getLastAcceptedFile().getParentFile().mkdirs();
}
FileUtils.saveToFile(getLastAcceptedFile(), getLastAcceptedContent(), getEncoding());
}
protected File getLastAcceptedFile() {
if (getFlexoResource() == null) {
return null;
}
return getFlexoResource().getLastAcceptedFile();
}
protected File getLastGeneratedFile() {
if (getFlexoResource() == null) {
return null;
}
return getFlexoResource().getLastGeneratedFile();
}
@Override
public void load() throws LoadGeneratedResourceIOException {
if (logger.isLoggable(Level.FINE)) {
logger.fine("Loading " + getFlexoResource().getFileName());
}
try {
currentDiskContent = new DiffSource(FileUtils.fileContents(getFile(), getEncoding()));
if (getLastGeneratedFile() != null) {
if (!getLastGeneratedFile().exists()) {
logger.warning("File does not exist: " + getLastGeneratedFile().getAbsolutePath() + ", creating empty one");
FileUtils.saveToFile(getLastGeneratedFile(), currentDiskContent.getSourceString(), getEncoding());
}
lastGeneratedContent = new DiffSource(FileUtils.fileContents(getLastGeneratedFile(), getEncoding()));
} else {
logger.warning("Could not find file null");
}
if (getLastAcceptedFile() != null) {
if (!getLastAcceptedFile().exists()) {
logger.warning("File does not exist: " + getLastAcceptedFile().getAbsolutePath() + ", creating empty one");
FileUtils.saveToFile(getLastAcceptedFile(), currentDiskContent.getSourceString(), getEncoding());
}
lastAcceptedContent = new DiffSource(FileUtils.fileContents(getLastAcceptedFile(), getEncoding()));
} else {
if (getLastAcceptedFile() != null) {
logger.warning("Could not find file " + getLastAcceptedFile().getAbsolutePath());
} else {
logger.warning("Could not find file null.");
}
}
hasDiskVersion = true;
} catch (IOException e) {
throw new LoadGeneratedResourceIOException(getFlexoResource(), e);
}
updateHistory();
}
/**
* Returns current generation This is "pure" generation (direct output from the generators): does NOT contain merge with changes
* registered for last accepted version
*
* @return a String representation
*/
public abstract String getCurrentGeneration();
/**
* Returns current generated content This content is obtained by merging current generation with changes registered for last accepted
* version
*
* @return a String representation
*/
public String getGeneratedMergedContent() {
if (!hasDiskVersion) {
return getCurrentGeneration();
} else {
return getGenerationMerge().getMergedSource().getSourceString();
}
}
/**
* Returns content to write on disk This content is the merge from the current generated and merge content with content extracted from
* the modified file on disk In case of an overriding, this is the overriden version
*
* @return
*/
public String getContentToWriteOnDisk() {
if (!hasDiskVersion) {
return getCurrentGeneration();
} else {
if (_overrideIsScheduled) {
return getContent(_overridenVersion);
}
if (getProject() != null && getProject().isComputeDiff()) {
return getResultFileMerge().getMergedSource().getSourceString();
} else {
return getCurrentGeneration();
}
}
}
public String getContent(ContentSource contentSource) {
if (contentSource.getType() == ContentSourceType.PureGeneration) {
return getCurrentGeneration();
} else if (contentSource.getType() == ContentSourceType.GeneratedMerge) {
return getGeneratedMergedContent();
} else if (contentSource.getType() == ContentSourceType.ResultFileMerge) {
return getContentToWriteOnDisk();
} else if (contentSource.getType() == ContentSourceType.ContentOnDisk) {
return getCurrentDiskContent();
} else if (contentSource.getType() == ContentSourceType.LastGenerated) {
return getLastGeneratedContent();
} else if (contentSource.getType() == ContentSourceType.LastAccepted) {
return getLastAcceptedContent();
} else if (contentSource.getType() == ContentSourceType.HistoryVersion) {
return getHistoryContent(contentSource.getVersion());
}
return null;
}
protected String getHistoryContent(CGVersionIdentifier versionId) {
try {
if (getHistory().versionWithId(versionId) != null) {
return getHistory().versionWithId(versionId).getContent();
} else {
return "Unable to access version " + versionId + " for file " + getFlexoResource().getFileName();
}
} catch (IOFlexoException e) {
e.printStackTrace();
return "Unable to access version " + versionId + " for file " + getFlexoResource().getFileName();
}
}
@Override
public final void generate() throws FlexoException {
if (!(getFlexoResource() instanceof GenerationAvailableFileResourceInterface)) {
throw new NotImplementedException("version_without_code_generator");
}
// Calling this method assume that this file was never generated.
// So, merges are not necessary, and file is simply overriden.
if (getFlexoResource().getGenerator() != null && getFlexoResource().getGenerator().getGeneratedCode() == null) {
if (getFlexoResource().getGenerator().getGenerationException() == null) {
logger.warning("Both GeneratedCode and exception are null: this is should never happen !");
} else {
throw (FlexoException) getFlexoResource().getGenerator().getGenerationException();
}
}
}
@Override
public final void regenerate() throws FlexoException {
if (!(getFlexoResource() instanceof GenerationAvailableFileResourceInterface)) {
throw new NotImplementedException("version_without_code_generator");
}
if (logger.isLoggable(Level.FINE)) {
logger.fine("regenerate() called in " + getClass().getName());
}
// OK, generator has performed its job, i have now to handle merges
if (lastGeneratedContent == null && getProject() != null && getProject().isComputeDiff()) {
throw new LoadGeneratedResourceIOException(getFlexoResource(), "Unable to access last generated content");
}
if (lastAcceptedContent == null && getProject() != null && getProject().isComputeDiff()) {
throw new LoadGeneratedResourceIOException(getFlexoResource(), "Unable to access last accepted content");
}
// Normally it's ok.
// I don't do it now, otherwise, i will erase merging selections
// rebuildMerges();
}
private void rebuildMerges() {
rebuildGenerationMerge();
rebuildResultFileMerge();
}
private void rebuildGenerationMerge() {
if (logger.isLoggable(Level.FINE)) {
logger.fine("rebuildGenerationMerge() called in " + getFlexoResource().getFileName());
}
if (_generationMerge != null) {
_generationMerge.delete();
_generationMerge = null;
}
if (getCurrentGeneration() != null) {
_generationMerge = new Merge(_getLastGeneratedContent(), new DiffSource(getCurrentGeneration()), _getLastAcceptedContent(),
getMergedDocumentType());
// logger.info("Resource "+getFlexoResource().getFileName()+" build generation merge");
// logger.info("_getLastGeneratedContent()="+_getLastGeneratedContent().getSourceString());
// logger.info("getCurrentGeneration()="+getCurrentGeneration());
// logger.info("_getLastAcceptedContent()="+_getLastAcceptedContent().getSourceString());
}
}
private void rebuildResultFileMerge() {
if (logger.isLoggable(Level.FINE)) {
logger.fine("rebuildResultFileMerge() called in " + getFlexoResource().getFileName());
}
if (_resultFileMerge != null) {
_resultFileMerge.delete();
_resultFileMerge = null;
}
if (getGenerationMerge() != null) {
_resultFileMerge = new ResultFileMerge(_getLastAcceptedContent(), getGenerationMerge(),
hasCurrentDiskContent() ? currentDiskContent : new DiffSource(""));
}
}
public static class ResultFileMerge extends Merge implements Observer {
private final Merge _generationMerge;
public ResultFileMerge(DiffSource lastAcceptedContent, Merge generationMerge, DiffSource currentDiskContent) {
super(lastAcceptedContent, generationMerge.getMergedSource(), currentDiskContent, generationMerge.getDocumentType());
_generationMerge = generationMerge;
_generationMerge.addObserver(this);
}
@Override
public void update(Observable o, Object arg) {
// Generation merge changed
if (logger.isLoggable(Level.FINE)) {
logger.fine("update() in ResultFileMerge, recompute merge");
}
recompute();
}
}
public String getCurrentDiskContent() {
if (currentDiskContent != null) {
return currentDiskContent.getSourceString();
} else {
logger.warning("getCurrentDiskContent() called for null content: " + getFlexoResource().getFileName());
return "Unable to access current disk content for " + getFlexoResource().getFileName();
}
}
public boolean hasCurrentDiskContent() {
return currentDiskContent != null;
}
public boolean hasLastAcceptedContent() {
return lastAcceptedContent != null;
}
public String getLastAcceptedContent() {
if (lastAcceptedContent != null) {
return lastAcceptedContent.getSourceString();
}
return FlexoLocalization.localizedForKey("unable_to_access_last_accepted_file")
+ "\n"
+ (getFlexoResource().getLastGeneratedFile() != null ? FlexoLocalization.localizedForKey("file") + " : "
+ getFlexoResource().getLastGeneratedFile().getAbsolutePath() : FlexoLocalization.localizedForKey("file") + " "
+ FlexoLocalization.localizedForKey("of_resource") + " " + getFlexoResource());
}
protected DiffSource _getLastGeneratedContent() {
if (lastGeneratedContent == null) {
if (getCurrentGeneration() != null) {
lastGeneratedContent = new DiffSource(getCurrentGeneration());
}
}
return lastGeneratedContent;
}
protected DiffSource _getLastAcceptedContent() {
if (lastAcceptedContent == null) {
if (hasCurrentDiskContent() || getProject().isComputeDiff()) {
lastAcceptedContent = new DiffSource(getCurrentDiskContent());
} else {
lastAcceptedContent = new DiffSource("");
}
}
return lastAcceptedContent;
}
public String getLastGeneratedContent() {
if (lastGeneratedContent != null) {
return lastGeneratedContent.getSourceString();
}
if (logger.isLoggable(Level.WARNING)) {
logger.warning("Last generated content not found for resource " + getFlexoResource());
}
return FlexoLocalization.localizedForKey("unable_to_access_last_generated_file")
+ "\n"
+ (getFlexoResource().getLastGeneratedFile() != null ? FlexoLocalization.localizedForKey("file") + " : "
+ getFlexoResource().getLastGeneratedFile().getAbsolutePath() : FlexoLocalization.localizedForKey("file") + " "
+ FlexoLocalization.localizedForKey("of_resource") + " " + getFlexoResource());
}
@Override
public boolean hasVersionOnDisk() {
return hasDiskVersion;
}
public void notifyVersionChangedOnDisk(String newDiskContent) {
currentDiskContent = new DiffSource(newDiskContent);
rebuildMerges();
}
@Override
public void notifyVersionChangedOnDisk() {
try {
load();
} catch (LoadGeneratedResourceIOException e) {
e.printStackTrace();
}
rebuildMerges();
}
public Merge getGenerationMerge() {
if (_generationMerge == null) {
rebuildGenerationMerge();
}
return _generationMerge;
}
public ResultFileMerge getResultFileMerge() {
if (_resultFileMerge == null) {
rebuildResultFileMerge();
}
return _resultFileMerge;
}
@Override
public void notifyRegenerated(CGContentRegenerated notification) {
if (logger.isLoggable(Level.FINE)) {
logger.fine("notifyRegenerated() called in " + getFlexoResource().getFileName());
}
if (!getFlexoResource().hasGenerationError()) {
rebuildMerges();
}
}
@Override
public void acceptDiskVersion() throws SaveGeneratedResourceIOException {
// Save file in history
if (manageHistory()) {
getHistory().storeCurrentFileInHistory(CGVersionIdentifier.VersionType.DiskUpdate);
}
lastAcceptedContent = new DiffSource(getCurrentDiskContent());
rebuildMerges();
try {
lastAcceptedContent = new DiffSource(getCurrentDiskContent());
saveLastAcceptedFile();
} catch (IOException e) {
throw new SaveGeneratedResourceIOException(getFlexoResource(), e);
}
}
private boolean _overrideIsScheduled = false;
private ContentSource _overridenVersion;
@Override
public void overrideWith(ContentSource version) {
_overrideIsScheduled = true;
_overridenVersion = version;
}
@Override
public boolean isOverrideScheduled() {
return _overrideIsScheduled;
}
@Override
public void cancelOverriding() {
_overrideIsScheduled = false;
_overridenVersion = null;
}
@Override
public ContentSource getScheduledOverrideVersion() {
return _overridenVersion;
}
@Override
public boolean doesGenerationKeepFileUnchanged() {
if (getResultFileMerge() == null) {
return false;
}
// logger.info("doesGenerationKeepFileUnchanged() called for "+getFlexoResource().getFileName()+
// " changes="+_generationMerge.getChanges().size());
return getResultFileMerge().getChanges().size() == 0;
}
@Override
public boolean isTriviallyMergable() {
return getGenerationMerge() != null && !getGenerationMerge().isReallyConflicting() && getResultFileMerge() != null
&& !getResultFileMerge().isReallyConflicting();
}
@Override
public boolean areAllConflictsResolved() {
return getGenerationMerge() != null && getGenerationMerge().isResolved() && getResultFileMerge() != null
&& getResultFileMerge().isResolved();
}
public MergedDocumentType getMergedDocumentType() {
return DefaultMergedDocumentType.getMergedDocumentType(getFileFormat());
}
public String getEncoding() {
return "UTF-8";
}
}