/*
* license-updater - Copyright (c) 2012 MSF. All rights reserved.
*
* This library is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General
* Public License as published by the Free Software Foundation; either version 2.1 of the License, or (at your option)
* any later version.
*
* This library 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 Lesser General Public License for more
* details.
*
* You should have received a copy of the GNU Lesser General Public License along with this library; if not, write to
* the Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA.
*/
package br.msf.netbeans.licenseupdater;
import br.msf.commons.collections.LinkedProperties;
import br.msf.commons.exception.io.RuntimeIOException;
import br.msf.commons.lang.EnhancedStringBuilder;
import br.msf.commons.lang.MatchEntry;
import br.msf.commons.netbeans.util.FileObjectUtils;
import br.msf.commons.netbeans.util.FileType;
import br.msf.commons.util.CharSequenceUtils;
import br.msf.commons.util.CollectionUtils;
import br.msf.commons.util.IOUtils;
import br.msf.netbeans.licenseupdater.LicenseUpdaterParams.UpdateMode;
import java.io.IOException;
import java.io.OutputStreamWriter;
import java.io.Writer;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.regex.Pattern;
import org.netbeans.api.queries.FileEncodingQuery;
import org.openide.filesystems.FileAlreadyLockedException;
import org.openide.filesystems.FileLock;
import org.openide.filesystems.FileObject;
/**
* <p>TODO
*
* @author Marcius da Silva da Fonseca (sf.marcius@gmail.com)
* @version 1.0
* @since license-updater-1.0
*/
public class LicenseController {
protected static final String NB_LICENSE_TEMPLATE = "To change this template, choose Tools | Templates";
protected static final Pattern JAVA_LICENSE = Pattern.compile("^\\s*/\\*(?:.|[\\n\\r])*?\\*/\\s*");
protected static final String JAVA_LICENSE_START = "/*";
protected static final String JAVA_LICENSE_LEADING = " * ";
protected static final String JAVA_LICENSE_END = " */";
protected LicenseUpdaterParams params;
public LicenseController(LicenseUpdaterParams params) {
this.params = params;
}
public LicenseController setParams(LicenseUpdaterParams params) {
this.params = params;
return this;
}
public LicenseController doTheTrick() {
if (params.getProject() == null || params.getBasePackage() == null) {
throw new IllegalStateException("Project or Base package not set!");
}
Collection<FileObject> files = FileObjectUtils.getFiles(params.getBasePackage().getFileObject(), true, FileType.JAVA_SRC);
for (FileObject f : files) {
updateJavaLicense(f);
}
return this;
}
private void updateJavaLicense(final FileObject fileObject) {
assert fileObject != null && fileObject.isData() && FileType.isJava(fileObject.getExt());
if (CharSequenceUtils.isBlank(params.getLicenseText())) {
return;
}
try {
EnhancedStringBuilder newSrc = getUpdatedSource(fileObject);
if (newSrc != null) {
if (!fileObject.canWrite()) {
throw new RuntimeIOException("Cant write to file: " + fileObject.getNameExt());
}
writeFile(fileObject, newSrc);
}
} catch (IOException ex) {
throw new RuntimeIOException(ex);
}
}
protected EnhancedStringBuilder getJavaLicense() {
return formatLicense(params.getLicenseText(), JAVA_LICENSE_START, JAVA_LICENSE_END, JAVA_LICENSE_LEADING, params.getMaxLineLength()).
replaceParams(getParams());
}
protected EnhancedStringBuilder formatLicense(final CharSequence text,
final String commentStart,
final String commentEnd,
final String leading,
final int maxLineLengh) {
assert CharSequenceUtils.isNotBlank(text) && CharSequenceUtils.isNotBlank(commentStart) && CharSequenceUtils.isNotBlank(commentEnd);
final EnhancedStringBuilder builder = new EnhancedStringBuilder(text).deletePattern("\r");
final Collection<MatchEntry> newLines = builder.findPattern("\n");
for (MatchEntry match : newLines) {
if (match.getStart() > 0 && isPeriodFinalization(builder.charAt(match.getStart() - 1))
&& '\n' != builder.charAt(match.getStart() + 1)) {
/* new lines not after a punctuation are removed */
builder.replace(match, " ");
}
}
builder.replacePattern(" +", " ");
final Collection<String> lines = builder.split("\n");
builder.clear().appendln(commentStart);
int max = maxLineLengh - leading.length();
for (CharSequence line : lines) {
if (line.length() <= max) {
// short line, just append the leading txt and the line itself
builder.append(leading).appendln(line);
} else {
// long line, we need to break it down
final List<String> subLines = breakdownLine(line.toString(), max);
for (String subLine : subLines) {
builder.append(leading).appendln(subLine);
}
}
}
return builder.appendln(commentEnd);
}
protected EnhancedStringBuilder getUpdatedSource(final FileObject fileObject) throws IOException {
final EnhancedStringBuilder builder = new EnhancedStringBuilder(fileObject.asText(FileEncodingQuery.getEncoding(fileObject).name()));
final Collection<MatchEntry> currentLicense = builder.findPattern(JAVA_LICENSE);
if (CollectionUtils.isNotEmpty(currentLicense)) {
MatchEntry me = currentLicense.iterator().next();
switch (params.getUpdateMode()) {
case ALL_FILES:
/* source already have a license header. lets replace it */
builder.replace(me, getJavaLicense());
break;
case WITHOUT_LICENSE_FILES:
/* we consider the default netbeans template as "file without license" */
if (builder.substring(me.getStart(), me.getEnd()).contains(NB_LICENSE_TEMPLATE)) {
builder.replace(me, getJavaLicense());
} else {
/* returning null sinalize that this FileObject will not be updated */
return null;
}
break;
}
} else {
/* source dont have a license header. lets place it */
builder.insert(0, getJavaLicense());
}
return builder;
}
protected void writeFile(final FileObject someFile, final CharSequence content) throws IOException {
FileLock lock;
try {
lock = someFile.lock();
} catch (FileAlreadyLockedException e) {
return;
}
Writer to = null;
try {
to = new OutputStreamWriter(someFile.getOutputStream(lock), FileEncodingQuery.getEncoding(someFile));
to.write(content.toString());
} finally {
IOUtils.closeQuietly(to);
lock.releaseLock();
}
}
protected List<String> breakdownLine(final String line, final int maxLenght) {
assert line != null && maxLenght > 0;
if (line.length() <= maxLenght || !CharSequenceUtils.containsPattern("\\s+", line)) {
return Arrays.asList(line);
}
final EnhancedStringBuilder builder = new EnhancedStringBuilder(line);
final List<String> lines = new ArrayList<String>();
while (builder.length() > maxLenght) {
int mark = -1;
// here we look for the last ' ' occurrence before the MAX_LINE_LEN
// or the first one if there is no ' ' before reaching the MAX_LINE_LEN limit
for (int i = 0; i < builder.length(); i++) {
if (builder.charAt(i) == ' ') {
if (i <= maxLenght || mark == -1) {
mark = i;
} else if (i > maxLenght) {
break;
}
}
}
if (mark >= 0) {
// if we found the ' ' occurrence we were looking for, we cut the line and create a subline with it
lines.add(builder.substring(0, mark).trim());
// remove the just added subline
builder.delete(0, mark);
} else {
// it must be a super dupper mega word with no space! dont break it!
lines.add(builder.trim().toString());
builder.clear();
}
}
if (builder.isNotBlank()) {
// if there is some left-over on the buffer, we create the last subline with it.
lines.add(builder.trim().toString());
}
return lines;
}
protected boolean isPeriodFinalization(final char c) {
// .!? are period finalizers.
// > can be a html tag limit, so we interpret it as a period finalizer too.
return ".!?>".indexOf(c) >= 0;
}
protected Map<String, String> getParams() {
Map<String, String> props = new LinkedHashMap<String, String>(LinkedProperties.getSystemProps());
props.put("project.name", params.getProjectName());
props.put("project.publisher", params.getProjectPublisher());
return props;
}
}