/*******************************************************************************
* Copyright (c) 2009 committers of openArchitectureWare and others.
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
* which accompanies this distribution, and is available at
* http://www.eclipse.org/legal/epl-v10.html
*
* Contributors:
* committers of openArchitectureWare - initial API and implementation
*******************************************************************************/
package org.eclipse.xtend.middleend.xpand.internal.xpandlib.pr;
import java.io.File;
import java.io.FileOutputStream;
import java.io.FileWriter;
import java.io.IOException;
import java.io.OutputStreamWriter;
import java.io.Writer;
import java.lang.ref.SoftReference;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.eclipse.xpand2.output.Outlet;
import org.eclipse.xtend.backend.util.Base64Codec;
import org.eclipse.xtend.backend.util.FileHelper;
import org.eclipse.xtend.backend.util.GenericFileFilter;
public class XpandProtectedRegionResolver {
public static final String XPAND_PROTECTED_REGION_RESOLVER = "XpandProtectedRegionResolver";
private static final String PROTECT_AFTER_ID = ")";
private static final String PROTECT_B64_AFTER_ID = "]";
private static final String PROTECT_B64_BEFORE_ID = "[";
private static final String PROTECT_BEFORE_ID = "(";
private static final String PROTECT_BEGIN = "PROTECTED REGION ID";
private static final String PROTECT_END = "PROTECTED REGION END";
private static final String PROTECT_START_END = "START";
private static final String ENABLED = "ENABLED";
private Map<String, XpandProtectedRegion> _regions;
private String _ignoreList;
private boolean _defaultExcludes;
private File[] _srcPaths;
private HashSet<String> _usedSet;
private Log log = LogFactory.getLog(XpandProtectedRegionResolver.class);
private String _fileEncoding;
private boolean _useBase64;
public class XpandProtectedRegion {
private final String _id;
private final int _startPos;
private final int _endPos;
private final File _file;
private final String _encoding;
private final boolean _disabled;
private final boolean _isBase64;
private final SoftReference<String> _body;
public XpandProtectedRegion(final String id, final int startPos, final int endPos,
final File file, final String encoding, boolean disabled, boolean useBase64,
final String body) {
super();
_id = id;
_startPos = startPos;
_endPos = endPos;
_file = file;
_encoding = encoding;
_disabled = disabled;
_isBase64 = useBase64;
_body = new SoftReference<String> (body);
}
public String getBody () {
return _body.get();
}
public String getBody (final String startComment, final String endComment) throws XpandProtectedRegionSyntaxException {
String body = _body.get();
if (body == null) {
try {
body = FileHelper.read (_file, _encoding).substring(_startPos, _endPos);
} catch (final IOException e) {
throw new RuntimeException ("Unexpected I/O exception (source files removed?)", e);
}
}
final int endCommentPos = body.indexOf (endComment);
if ((endCommentPos < 0) || (body.substring (0, endCommentPos).trim().length() > 0))
throw new XpandProtectedRegionSyntaxException("Start of protected region " + _id
+ " does not end with comment " + endComment);
final int startCommentPos = body.lastIndexOf (startComment);
if ((startCommentPos < 0)
|| (body.substring(startCommentPos + startComment.length()).trim().length() > 0))
throw new XpandProtectedRegionSyntaxException("End of protected region " + _id
+ " does not start with comment " + startComment + " \"" +body.substring(startCommentPos + startComment.length()).trim() + "\"");
return body.substring(endCommentPos + endComment.length(), startCommentPos);
}
public String getId() {
return _id;
}
public File getFile() {
return _file;
}
public String getStartString(final String startComment, final String endComment) {
if (_isBase64) {
try {
return (startComment + PROTECT_BEGIN + PROTECT_B64_BEFORE_ID + Base64Codec.toString(_id)
+ PROTECT_B64_AFTER_ID + " " + (!_disabled ? ENABLED + " " : "") + PROTECT_START_END + endComment);
} catch (final IOException ie) {
// fallback to old style if BASE64Encoder fails
}
}
return (startComment + PROTECT_BEGIN + PROTECT_BEFORE_ID + _id + PROTECT_AFTER_ID + " "
+ (!_disabled ? ENABLED + " " : "") + PROTECT_START_END + endComment);
}
public String getEndString(final String startComment, final String endComment) {
return startComment + PROTECT_END + endComment;
}
}
public XpandProtectedRegionResolver () {
super();
}
public XpandProtectedRegionResolver (String ignoreList,
boolean defaultExcludes, List<Outlet> outlets, String fileEncoding,
boolean useBase64) {
super();
_ignoreList = ignoreList;
_defaultExcludes = defaultExcludes;
_srcPaths = new File [outlets.size()];
for (int i = 0; i < _srcPaths.length; i++) {
_srcPaths[i] = new File (outlets.get(i).getPath());
}
_fileEncoding = fileEncoding;
_useBase64 = useBase64;
}
/**
* Initializes the XpandProtectedRegionResolver. This starts the scan over all configured paths (property 'srcPaths').
* <p>
* A second call (already initialized) to this method will return immediately. The method should be called lazily.
*
* @throws DuplicateXpandProtectedRegionException If a Protected Region Id is detected the second time, i.e. it is not unique.
*/
public void init() throws DuplicateXpandProtectedRegionException {
// Already initialized?
if (_regions != null) {
return;
}
// Initialize the Protected Region map
_regions = new HashMap<String, XpandProtectedRegion> ();
_usedSet = new HashSet<String> ();
if (_srcPaths==null) {
log.warn ("No source paths configured for scanning.");
// abort
return;
}
long time = 0;
long fileCount = 0;
if (log.isInfoEnabled ()) {
log.info ("Source scan started ...");
time = System.currentTimeMillis();
}
// create the file filter
final GenericFileFilter filter = new GenericFileFilter(_ignoreList, _defaultExcludes);
// Scan all configured paths
for (int i = 0; i < _srcPaths.length; i += 1) {
try {
// retrieve (recursive) all files from a path matching the configured filter
final File[] files = FileHelper.getAllFiles (_srcPaths[i], filter);
fileCount += files.length;
// scan all files
for (int j = 0; j < files.length; j += 1) {
// retrieve the Protected Regions from the current file
final Iterator<XpandProtectedRegion> regions = retrieveProtectedRegions (files[j], _fileEncoding, _useBase64).iterator();
while (regions.hasNext()) {
final XpandProtectedRegion region = regions.next ();
register (region);
}
}
} catch (final IOException e) {
throw new RuntimeException ("Unexpected I/O exception", e);
} catch (final XpandProtectedRegionSyntaxException e) {
throw new RuntimeException (e.getMessage (), e);
}
}
if (log.isInfoEnabled()) {
time = System.currentTimeMillis () - time;
log.info("Source scan finished in " + (time / 1000.0) + "s");
log.info("Files scanned: " + fileCount);
log.info("Regions found: " + _regions.size());
}
}
public XpandProtectedRegion getProtectedRegion (final String id) {
try {
init ();
} catch (DuplicateXpandProtectedRegionException e) {
log.warn (e.getMessage (), e);
}
if (!_usedSet.add(id)) {
// id was not added to usedSet -> id was already queried before!
log.warn ("Protected region with ID '" + id + "' referenced more than once");
}
return _regions.get (id);
}
/**
* Factory method to create an XpandProtectedRegion
*/
public XpandProtectedRegion createProtectedRegion(final String id, final boolean disabled) {
return new XpandProtectedRegion(id, 0, 0, null, null, disabled, _useBase64, null);
}
/**
* Register a protected region under it's ID
* @param region
*/
public void register (final XpandProtectedRegion region) throws DuplicateXpandProtectedRegionException {
if (!isRegistered(region)) {
_regions.put (region.getId(), region);
} else {
final String id = region.getId();
throw new DuplicateXpandProtectedRegionException ("The protected region Id '" + id + "' occuring in files " + region.getFile ()
+ " and " + _regions.get (id).getFile ()
+ " is not unique");
}
}
public boolean isRegistered (final XpandProtectedRegion region) {
return _regions.containsKey (region.getId());
}
/**
* Retrieves all Protected Regions from a source file.
* @param file The source file to scan.
* @return All found Protected Regions in the specified file.
* @throws XpandProtectedRegionSyntaxException If one of the Protected Regions in the file is incomplete or invalid.
* @throws IOException On errors occuring when reading the file
*/
private Collection<XpandProtectedRegion> retrieveProtectedRegions (final File file, final String encoding, final boolean useBASE64) throws XpandProtectedRegionSyntaxException, IOException {
final List<XpandProtectedRegion> regions = new ArrayList<XpandProtectedRegion>();
final String source = FileHelper.read(file, encoding);
final int beginLength = PROTECT_BEGIN.length();
final int startEndLength = PROTECT_START_END.length();
final int idBeginLength = PROTECT_BEFORE_ID.length();
final int idEndLength = PROTECT_AFTER_ID.length();
int start = source.indexOf (PROTECT_BEGIN);
while (start >= 0) {
final int blockStart = start + beginLength;
boolean isB64 = false;
int idStart = source.indexOf (PROTECT_BEFORE_ID, blockStart);
if (idStart != blockStart) {
idStart = source.indexOf (PROTECT_B64_BEFORE_ID, blockStart);
isB64 = true;
}
idStart += idBeginLength;
final int end = source.indexOf (PROTECT_END, idStart);
final int next = source.indexOf (PROTECT_BEGIN, idStart);
if ((end < 0) || ((next >= 0) && (next < end)))
throw new XpandProtectedRegionSyntaxException ("Protected region at index " + start + " in file '" + file
+ "' is incomplete");
final int idEnd = source.indexOf (isB64 ? PROTECT_B64_AFTER_ID : PROTECT_AFTER_ID, idStart);
if ((idEnd <= idStart) || (end < idEnd))
throw new XpandProtectedRegionSyntaxException ("Protected region Id at index " + start + " in file '" + file
+ "' is incomplete");
String id = new String(source.substring (idStart, idEnd));
if (isB64) {
try {
id = new String (Base64Codec.toByteArray(id));
} catch (final IOException ie) {
throw new XpandProtectedRegionSyntaxException ("Protected region Id at index " + start + " in file '"
+ file + "' is incomplete", ie);
}
}
final int startEnd = source.indexOf (PROTECT_START_END, idEnd + idEndLength);
if (end < startEnd)
throw new XpandProtectedRegionSyntaxException ("Protected region start at index " + start + " in file '"
+ file + "' is incomplete");
final String type = new String(source.substring (idEnd + idEndLength, startEnd).trim().toUpperCase());
if (!(type.equals("") || type.equals(ENABLED)))
throw new XpandProtectedRegionSyntaxException ("Protected region start at index " + start + " in file "
+ file + " has illegal type '" + type+ "'");
if (type.equals(ENABLED)) {
final String body = new String (source.substring (startEnd + startEndLength, end));
regions.add (new XpandProtectedRegion (id, startEnd + startEndLength, end, file, encoding, false, useBASE64, body));
}
start = next;
}
return regions;
}
/**
* Dumps all known protected regions to files. For each protected region a file is created.
* @param dumpPath Directory where the dump files are created within.
*/
public void reportRegions(final File dumpPath) {
final int unused = _regions.size() - _usedSet.size();
if (unused > 0) {
log.warn("There are " + unused + " unused Regions:");
if (dumpPath != null) {
dumpPath.mkdirs ();
}
for (final Iterator<XpandProtectedRegion> regions = _regions.values().iterator(); regions.hasNext();) {
final XpandProtectedRegion region = regions.next ();
final String id = region.getId ();
if (!_usedSet.contains (id)) {
log.warn ("File: " + region.getFile ());
log.warn ("ID: " + id);
try {
if (dumpPath != null) {
final File file = new File (dumpPath, Base64Codec.toString(id));
Writer writer;
if (_fileEncoding == null) {
writer = new FileWriter (file);
} else {
writer = new OutputStreamWriter (new FileOutputStream (file), _fileEncoding);
}
writer.write(region.getStartString("", ""));
writer.write(region.getBody("", ""));
writer.write(region.getEndString("", ""));
writer.close();
}
} catch (final IOException e) {
throw new RuntimeException ("Unexpected I/O exception", e);
} catch (final XpandProtectedRegionSyntaxException e) {
log.error(e.getMessage(), e);
}
}
}
}
}
/**
* This flag determines whether default file exclusion patterns should be used.
* @param defaultExcludes <code>true</code>: Use default file exclusion patterns, <code>false</code>: ignore them, just use
* the patterns specified by {@link #setIgnoreList(String) ignoreList}
* @see Xpand reference manual
*/
public void setDefaultExcludes(final boolean defaultExcludes) {
_defaultExcludes = defaultExcludes;
}
/**
* Sets the file encoding to be used when reading files.
* @param encoding A valid encoding string.
*/
public void setFileEncoding(final String encoding) {
_fileEncoding = encoding;
}
/**
* Sets a custom list of file patterns that should be filtered during scanning of source files
* and directories.
* @param ignoreList A comma separated list of file patterns to ignore during scan.
*/
public void setIgnoreList(final String ignoreList) {
_ignoreList = ignoreList;
}
/**
* Sets the source paths that should be scanned.
* @param srcPathsAsString A comma separated list of directory paths.
* @throws IllegalArgumentException If one of the passed arguments is not a directory or does not exist
*/
public void setSrcPathes(final String srcPathsAsString) throws IllegalArgumentException {
// Split the paths and initialize the
// file array 'srcPaths' from it
if ("".equals(srcPathsAsString)) {
_srcPaths = new File[0];
} else {
final String[] s = srcPathsAsString.split(",");
_srcPaths = new File[s.length];
for (int i = 0; i < _srcPaths.length; i++) {
_srcPaths[i] = new File(s[i].trim());
// The configured path must point to an existing directory
if (!_srcPaths[i].isDirectory()) {
throw new IllegalArgumentException("Source path component " + _srcPaths[i]
+ " not found or no directory");
}
}
}
}
public void setUseBASE64(final boolean useBASE64) {
_useBase64 = useBASE64;
}
}