blob: 76ace08ea15847f16d74f4551bf96f6ae18cf9e1 [file] [log] [blame]
/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
package org.netbeans.modules.java.guards;
import java.nio.CharBuffer;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.LinkedList;
import java.util.List;
import java.util.MissingResourceException;
import java.util.logging.Level;
import java.util.logging.Logger;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import javax.swing.text.BadLocationException;
import org.netbeans.api.editor.guards.GuardedSection;
import org.openide.util.NbBundle;
/**
*
* @author Jan Pokorsky
*/
final class JavaGuardedReader {
/** The prefix of all magic strings */
final static String MAGIC_PREFIX = "//GEN-"; // NOI18N
Pattern magicsAsRE;
private static final int LONGEST_ITEM = 10;
/**
* There are three possible ways how the comments defining the guarded
* sections can be processed during reading through the reader:
* 1. Keep the comments. They will be visible to the user in source editor.
* 2. Replace the comments with spaces to hide them from user but to preserve
* overall length and positions.
* 3. Remove the comments completely.
*
* Option #3 causes that offsets inside the editor differ from the offsets on disk,
* which then breaks many clients that create PositionRefs for a closed file and then
* use it for an opened file, and vice versa.
*
* Default is #2.
*/
private static boolean KEEP_GUARD_COMMENTS // not final only for tests
= getPresetValue("KEEP_GUARD_COMMENTS", false); // NOI18N
/** The list of the SectionsDesc. */
private final LinkedList<SectionDescriptor> list;
private final JavaGuardedSectionsProvider provider;
/** Creates a new instance of JavaGuardedReader */
public JavaGuardedReader(JavaGuardedSectionsProvider provider) {
list = new LinkedList<SectionDescriptor>();
this.provider = provider;
}
public List<GuardedSection> getGuardedSections() {
return fillSections(list);
}
public char[] translateToCharBuff(char[] readBuff) {
char[] charBuff = new char[readBuff.length];
// points to first unused cell in charBuff
int charBuffPtr = 0;
int stop = readBuff.length - 1;
// read char
int c;
// ptr to first not processed char in readBuff
int i = 0;
// points to a character right after a newline
int lastNewLine = 0;
// final automata
int fatpos = 0;
final int MAGICLEN = MAGIC_PREFIX.length();
//process newlines so only '\n' appears in the charBuff
//count all kinds of newlines - most used will be used on save
while (i < stop) {
c = readBuff[i];
if (c == '\n') {
lastNewLine = charBuffPtr;
}
charBuff[charBuffPtr++] = readBuff[i++];
switch (fatpos) {
case 0:
if (c == '/') {
fatpos++;
} else {
fatpos = 0;
}
break;
case 1:
if (c == '/') {
fatpos++;
} else {
fatpos = 0;
}
break;
case 2:
if (c == 'G') {
fatpos++;
} else if (c == '/') {
fatpos = 2; // what if /////GEN-xxx?
} else {
fatpos = 0;
}
break;
case 3:
if (c == 'E') {
fatpos++;
} else {
fatpos = 0;
}
break;
case 4:
if (c == 'N') {
fatpos++;
} else {
fatpos = 0;
}
break;
case 5:
if (c == '-') {
fatpos++;
} else {
fatpos = 0;
}
break;
default:
fatpos = 0;
}
// "//GEN-" was reached at this time
if (fatpos == MAGICLEN) {
fatpos = 0;
Pattern magics = getMagicsAsRE();
int searchLen = Math.min(LONGEST_ITEM, readBuff.length - i);
CharBuffer chi = CharBuffer.wrap(readBuff, i, searchLen);
Matcher matcher = magics.matcher(chi);
if (matcher.find()) {
String match = matcher.group();
charBuffPtr -= MAGICLEN;
i += match.length();
int toNl = toNewLine(i, readBuff);
int sectionSize = MAGICLEN+match.length()+toNl;
// if (!justFilter) {
// System.out.println("## MATCH: '" + match.substring(0, match.length() - 1) + "'");
SectionDescriptor desc = new SectionDescriptor(
GuardTag.valueOf(match.substring(0, match.length() - 1)), //XXX catch IAE
String.valueOf(readBuff, i, toNl),
lastNewLine + 1,
charBuffPtr + sectionSize
);
// new SectionDescriptor(GuardTag.valueOf(match.substring(0, match.length() - 1))); //XXX catch IAE
// desc.begin = lastNewLine;
// desc.end = charBuffPtr + sectionSize + 1;
// desc.name = new String(readBuff, i, toNl);
list.add(desc);
// }
if (KEEP_GUARD_COMMENTS) { // keep guard comment (content unchanged)
i -= match.length();
charBuffPtr += MAGICLEN;
} else {
i += toNl;
Arrays.fill(charBuff,charBuffPtr,charBuffPtr+sectionSize,' ');
charBuffPtr+=sectionSize;
}
}
}
}
if (i == stop) {
// c = readBuff[i];
// switch(c) {
// case (int) '\n':
//
// newLineTypes[NewLine.N.ordinal()]++;
//
// charBuff[charBuffPtr++] = '\n';
//
// break;
// case (int) '\r':
//
// newLineTypes[NewLine.R.ordinal()]++;
//
// charBuff[charBuffPtr++] = '\n';
//
// break;
// default:
//
charBuff[charBuffPtr++] = readBuff[i++];
// }
}
// repair last SectionDesc
if (/*!justFilter && */(list.size() > 0)) {
SectionDescriptor desc = (SectionDescriptor) list.getLast();
if (desc.getEnd() > charBuffPtr) {
desc.setEnd(charBuffPtr);
}
}
char[] res;
if (charBuffPtr != charBuff.length) {
res = new char[charBuffPtr];
System.arraycopy(charBuff, 0, res, 0, charBuffPtr);
} else {
res = charBuff;
}
return res;
}
/** @return searching engine for magics */
final Pattern getMagicsAsRE() {
if (magicsAsRE == null) {
magicsAsRE = Pattern.compile(makeOrRegexp());
}
return magicsAsRE;
}
/** Makes or regular expression for magics */
final String makeOrRegexp() {
StringBuilder sb = new StringBuilder(100);
// final int len = MAGIC_PREFIX.length();
for (GuardTag t: GuardTag.values()) {
sb.append(t.name() + ':');
sb.append('|');
}
return sb.substring(0, sb.length() - 1);
}
/** Searches for newline from i */
static int toNewLine(int i, char[] readBuff) {
int c;
int counter = i;
final int len = readBuff.length;
while (counter < len) {
c = readBuff[counter++];
if (c == '\r' || c == '\n') {
counter--;
break;
}
}
return counter - i;
}
/** Takes the section descriptors from the GuardedReader and
* fills the table 'sections', also marks as guarded all sections
* in the given document.
* @param is Where to take the guarded section descriptions.
* @param doc Where to mark guarded.
*/
List<GuardedSection> fillSections(List<SectionDescriptor> descs) {
SectionDescriptor descBegin = null;
List<GuardedSection> sections = new ArrayList<GuardedSection>(descs.size());
for (SectionDescriptor descCurrent: descs) {
try {
GuardedSection sect = null;
switch (descCurrent.getType()) {
case LINE:
sect = provider.createSimpleSection(
descCurrent.getName(),
descCurrent.getBegin(),
descCurrent.getEnd()
);
break;
case BEGIN:
case HEADER:
case FIRST:
descBegin = descCurrent;
break;
case HEADEREND:
if ((descBegin != null) &&
((descBegin.getType() == GuardTag.HEADER) || (descBegin.getType() == GuardTag.FIRST)) &&
(descCurrent.getName().equals(descBegin.getName()))
) {
descBegin.setEnd(descCurrent.getEnd());
}
else {
//SYNTAX ERROR - ignore it.
descBegin = null;
}
break;
case END:
case LAST:
if ((descBegin != null) && (descBegin.getName().equals(descCurrent.getName()))) {
if ((descBegin.getType() == GuardTag.BEGIN) && (descCurrent.getType() == GuardTag.END)) {
// simple section
sect = provider.createSimpleSection(
descCurrent.getName(),
descBegin.getBegin(), descCurrent.getEnd()
);
break;
}
if (((descBegin.getType() == GuardTag.FIRST) && (descCurrent.getType() == GuardTag.LAST)) ||
((descBegin.getType() == GuardTag.HEADER) && (descCurrent.getType() == GuardTag.END))) {
// interior section
sect = provider.createInteriorSection(
descCurrent.getName(),
descBegin.getBegin(), descBegin.getEnd(),
descCurrent.getBegin(), descCurrent.getEnd()
);
break;
}
}
//SYNTAX ERROR - ignore it.
descBegin = null;
break;
}
if (sect != null) {
sections.add(sect);
}
} catch (BadLocationException ex) {
Logger.getLogger(JavaGuardedReader.class.getName()).log(Level.SEVERE, ex.getLocalizedMessage(), ex);
}
}
return sections;
}
// static String magic(GuardTag tag) {
// return MAGIC_PREFIX + tag.name() + ':';
// }
private static boolean getPresetValue(String key, boolean defaultValue) {
try {
String s = NbBundle.getMessage(JavaGuardedReader.class, key);
return "true".equals(s.toLowerCase()); // NOI18N
} catch( MissingResourceException ex) { // ignore
}
return defaultValue;
}
static boolean getKeepGuardedComments() {
return KEEP_GUARD_COMMENTS;
}
static void setKeepGuardCommentsForTest(boolean keep) {
KEEP_GUARD_COMMENTS = keep;
}
}