/******************************************************************************* * Copyright (c) 2005, 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.internal.xpand2.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 java.util.Set; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; import org.eclipse.internal.xpand2.pr.util.BASE64; import org.eclipse.internal.xpand2.pr.util.FSIO; import org.eclipse.internal.xpand2.pr.util.GenericFileFilter; /** * Default implementation of the {@link ProtectedRegionResolver} interface. */ public class ProtectedRegionResolverImpl implements ProtectedRegionResolver { private static final Log LOG = LogFactory.getLog(ProtectedRegionResolverImpl.class); private static final String ENABLED = "ENABLED"; public static class ProtectedRegionImpl implements ProtectedRegion { private SoftReference<String> body; private String fileEncoding; private int endIndex; private File file; private String id; private int startIndex; private boolean disabled = true; private boolean useBASE64; public ProtectedRegionImpl(final String id, final boolean disabled, final File file, final String fileEncoding, final boolean useBASE64, final int startIndex, final int endIndex, final String body) { this.id = id; this.disabled = disabled; this.file = file; this.fileEncoding = fileEncoding; this.useBASE64 = useBASE64; this.startIndex = startIndex; this.endIndex = endIndex; this.body = new SoftReference<String>(body); } public void setBody(final String body) { this.body = new SoftReference<String>(body); } public String getBody(final String startComment, final String endComment) throws ProtectedRegionSyntaxException { String body = this.body.get(); if (body == null) { try { body = FSIO.readSingleFile(file, fileEncoding).substring(startIndex, endIndex); } catch (final IOException e) { throw new RuntimeException("Unexpected I/O exception (source files removed?)", e); } } final int endCommentIndex = body.indexOf(endComment); if ((endCommentIndex < 0) || (body.substring(0, endCommentIndex).trim().length() > 0)) throw new ProtectedRegionSyntaxException("Start of protected region " + id + " does not end with comment " + endComment); final int startCommentIndex = body.lastIndexOf(startComment); if ((startCommentIndex < 0) || (body.substring(startCommentIndex + startComment.length()).trim().length() > 0)) throw new ProtectedRegionSyntaxException("End of protected region " + id + " does not start with comment " + startComment); return body.substring(endCommentIndex + endComment.length(), startCommentIndex); } public String getEndString(final String startComment, final String endComment) { return startComment + PROTECT_END + endComment; } public File getFile() { return file; } public String getId() { return id; } public String getFileEncoding() { return fileEncoding; } public int getEndIndex() { return endIndex; } public int getStartIndex() { return startIndex; } public boolean isDisabled() { return disabled; } public boolean isUseBASE64() { return useBASE64; } public String getStartString(final String startComment, final String endComment) { if (useBASE64) { try { return (startComment + PROTECT_BEGIN + PROTECT_B64_BEFORE_ID + BASE64.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); } } 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 final Log log = LogFactory.getLog(getClass()); private File[] srcPaths = null; private boolean defaultExcludes = true; protected boolean useBASE64 = false; protected String encoding; private String ignoreList = null; /** * This map stores all scanned protected regions. * <p> * Key: Protected Region ID<br> * Value: The Protected Region */ private Map<String, ProtectedRegionImpl> regionMap = null; /** * All already queried Protected Region Ids. Is used for detecting ambigious usage of * Protected Regions. */ private Set<String> usedSet = null; /** * 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 ProtectedRegionSyntaxException If one of the Protected Regions in the file is incomplete or invalid. * @throws IOException On errors occuring when reading the file */ protected Collection<ProtectedRegionImpl> getAllRegions(final File file) throws ProtectedRegionSyntaxException, IOException { final List<ProtectedRegionImpl> regions = new ArrayList<ProtectedRegionImpl>(); final String source = FSIO.readSingleFile(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) { // IP System.out.println("IDSTART:"+idStart); 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 ProtectedRegionSyntaxException("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 ProtectedRegionSyntaxException("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(BASE64.toByteArray(id)); } catch (final IOException ie) { throw new ProtectedRegionSyntaxException("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 ProtectedRegionSyntaxException("Protected region start at index " + start + " in file '" + file + "' is incomplete"); String type = new String(source.substring(idEnd + idEndLength, startEnd).trim().toUpperCase()); // If start marker is wrapped if (type.startsWith(ENABLED) && type.substring(ENABLED.length()).contains("\n*")) { type = ENABLED; } if (!(type.equals("") || type.equals(ENABLED))) throw new ProtectedRegionSyntaxException("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 ProtectedRegionImpl(id, false, file, encoding, useBASE64, startEnd + startEndLength, end, body)); } start = next; } return regions; } public ProtectedRegion createProtectedRegion(final String id, final boolean disabled) { return new ProtectedRegionImpl(id, disabled, null, null, useBASE64, 0, 0, null); } public ProtectedRegion getProtectedRegion(final String id) { init(); // if (!usedSet.isEmpty()) { // Fix:No error if Handle is not used // return null; // } 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 regionMap.get(id); } /** * Initializes the ProtectedRegionResolver. This starts the scan over all configured paths (property 'srcPaths'). * <p> * A second call (already initialized) to this method will return immediately. * * @throws IllegalStateException If a Protected Region Id is detected the second time, i.e. it is not unique. */ public void init() throws IllegalStateException { // Already initialized? if (regionMap != null) { return; } // Initialize the Protected Region map regionMap = new HashMap<String, ProtectedRegionImpl>(); 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 = FSIO.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<ProtectedRegionImpl> regions = getAllRegions(files[j]).iterator(); while (regions.hasNext()) { final ProtectedRegionImpl region = regions.next(); final String id = region.getId(); // check for non-uniqueness of a Protected Region Id if (regionMap.containsKey(id)) { throw new IllegalStateException ("Id '" + id + "' occuring in files " + region.getFile() + " and " + regionMap.get(id).getFile() + " is not unique"); } // Store this region regionMap.put(id, region); } } } catch (final IOException e) { throw new RuntimeException("Unexpected I/O exception", e); } catch (final ProtectedRegionSyntaxException 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: " + regionMap.size()); } } /** * 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 = regionMap.size() - usedSet.size(); if (unused > 0) { log.warn("There are " + unused + " unused Regions:"); if (dumpPath != null) { dumpPath.mkdirs(); } for (final Iterator<ProtectedRegionImpl> regions = regionMap.values().iterator(); regions.hasNext();) { final ProtectedRegionImpl 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, BASE64.toString(id)); Writer writer; if (encoding == null) { writer = new FileWriter(file); } else { writer = new OutputStreamWriter(new FileOutputStream(file), encoding); } 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 ProtectedRegionSyntaxException 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) { this.defaultExcludes = defaultExcludes; } /** * Sets the file encoding to be used when reading files. * @param encoding A valid encoding string. */ public void setFileEncoding(final String encoding) { this.encoding = 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) { this.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)) { this.srcPaths = new File[0]; } else { final String[] s = srcPathsAsString.split(","); final List<File> validPaths = new ArrayList<File>(s.length); for (int i = 0; i < s.length; i++) { File dir = new File(s[i].trim()); // The configured path must point to an existing directory if (dir.isDirectory()) { validPaths.add(dir); } else { final String msg = "Ignoring non-existing protected region path " + dir.getAbsolutePath(); LOG.warn(msg); throw new IllegalArgumentException(msg); } } this.srcPaths = validPaths.toArray(new File[]{}); } } public void setUseBASE64(final boolean useBASE64) { this.useBASE64 = useBASE64; } }