1.1 --- a/php.smarty/manifest.mf Fri Jul 08 10:53:41 2011 +0200
1.2 +++ b/php.smarty/manifest.mf Sun Jul 17 01:29:31 2011 +0200
1.3 @@ -2,5 +2,5 @@
1.4 OpenIDE-Module: org.netbeans.modules.php.smarty
1.5 OpenIDE-Module-Layer: org/netbeans/modules/php/smarty/resources/layer.xml
1.6 OpenIDE-Module-Localizing-Bundle: org/netbeans/modules/php/smarty/resources/Bundle.properties
1.7 -OpenIDE-Module-Specification-Version: 1.55
1.8 +OpenIDE-Module-Specification-Version: 1.56
1.9
2.1 --- a/php.smarty/src/org/netbeans/modules/php/smarty/editor/Bundle.properties Fri Jul 08 10:53:41 2011 +0200
2.2 +++ b/php.smarty/src/org/netbeans/modules/php/smarty/editor/Bundle.properties Sun Jul 17 01:29:31 2011 +0200
2.3 @@ -37,3 +37,21 @@
2.4
2.5 #TplDataLoader
2.6 PROP_TplLoader_Name=TPL Objects
2.7 +
2.8 +# TplEditorSupport
2.9 +# {0} document name
2.10 +# {1} encoding
2.11 +# {2} original encoding of the file when loaded
2.12 +# {3} "the original" or empty string depending on context
2.13 +MSG_unsupportedEncodingSave=<html>The encoding {1} specified in meta tag of the document {0} is invalid<br> or the document contains characters which cannot be saved using this encoding.<br> Do you want to save the file using{3} <b>{2}</b> encoding?</html>
2.14 +
2.15 +# TplEditorSupport
2.16 +# {0} document name
2.17 +# {1} encoding
2.18 +# {2} alternative encoding
2.19 +MSG_unsupportedEncodingLoad=<html>The encoding {1} specified in meta tag of the document {0} is invalid.<br>Do you want to load the file using <b>{2}</b> encoding?</html>
2.20 +
2.21 +# TplEditorSupport
2.22 +# {0} = name of file
2.23 +# {1} = the bad encoding
2.24 +MSG_badCharConversionSave=<html>The {0} contains characters which will probably be damaged during conversion to the {1} character set.<br> Do you want to save the file using <b>UTF-8</b> character set?</html>
3.1 --- a/php.smarty/src/org/netbeans/modules/php/smarty/editor/TplDataObject.java Fri Jul 08 10:53:41 2011 +0200
3.2 +++ b/php.smarty/src/org/netbeans/modules/php/smarty/editor/TplDataObject.java Sun Jul 17 01:29:31 2011 +0200
3.3 @@ -4,8 +4,32 @@
3.4 */
3.5 package org.netbeans.modules.php.smarty.editor;
3.6
3.7 +import java.io.ByteArrayInputStream;
3.8 import java.io.IOException;
3.9 +import java.io.InputStream;
3.10 +import java.io.InputStreamReader;
3.11 +import java.io.UnsupportedEncodingException;
3.12 +import java.nio.ByteBuffer;
3.13 +import java.nio.CharBuffer;
3.14 +import java.nio.charset.Charset;
3.15 +import java.nio.charset.CharsetDecoder;
3.16 +import java.nio.charset.CharsetEncoder;
3.17 +import java.nio.charset.CoderResult;
3.18 +import java.nio.charset.IllegalCharsetNameException;
3.19 +import java.nio.charset.UnsupportedCharsetException;
3.20 +import java.util.concurrent.atomic.AtomicBoolean;
3.21 +import java.util.logging.Level;
3.22 +import java.util.logging.Logger;
3.23 +import org.netbeans.api.html.lexer.HTMLTokenId;
3.24 +import org.netbeans.api.lexer.Token;
3.25 +import org.netbeans.api.lexer.TokenHierarchy;
3.26 +import org.netbeans.api.lexer.TokenSequence;
3.27 +import org.netbeans.api.queries.FileEncodingQuery;
3.28 +import org.netbeans.spi.queries.FileEncodingQueryImplementation;
3.29 +import org.openide.filesystems.FileChangeAdapter;
3.30 +import org.openide.filesystems.FileEvent;
3.31 import org.openide.filesystems.FileObject;
3.32 +import org.openide.filesystems.FileUtil;
3.33 import org.openide.loaders.DataNode;
3.34 import org.openide.loaders.DataObjectExistsException;
3.35 import org.openide.loaders.MultiDataObject;
3.36 @@ -14,12 +38,24 @@
3.37 import org.openide.nodes.CookieSet;
3.38 import org.openide.nodes.Node;
3.39 import org.openide.nodes.Children;
3.40 +import org.openide.util.Exceptions;
3.41 import org.openide.util.Lookup;
3.42 import org.openide.util.UserCancelException;
3.43
3.44 +/**
3.45 + * Most code of this class was get from HtmlDataObject - especially encoding support.
3.46 + *
3.47 + * @author Martin Fousek <marfous@netbeans.org>
3.48 + */
3.49 public class TplDataObject extends MultiDataObject implements CookieSet.Factory {
3.50
3.51 private transient TplEditorSupport tplEditorSupport;
3.52 +
3.53 + public static final String DEFAULT_ENCODING = new InputStreamReader(System.in).getEncoding();
3.54 + //constants used when finding tpl (html) document content type
3.55 + private static final String CHARSET_DECL = "CHARSET="; //NOI18N
3.56 +
3.57 + private static final Logger LOG = Logger.getLogger(TplDataObject.class.getName());
3.58
3.59 public TplDataObject(FileObject pf, MultiFileLoader loader) throws DataObjectExistsException, IOException {
3.60 super(pf, loader);
3.61 @@ -31,12 +67,15 @@
3.62 public void saveAs( FileObject folder, String fileName ) throws IOException {
3.63 TplEditorSupport es = getCookie( TplEditorSupport.class );
3.64 try {
3.65 + es.updateEncoding();
3.66 es.saveAs( folder, fileName );
3.67 } catch (UserCancelException e) {
3.68 //ignore, just not save anything
3.69 }
3.70 }
3.71 });
3.72 +
3.73 + set.assign(FileEncodingQueryImplementation.class, new FileEncodingQueryImpl());
3.74 }
3.75
3.76 @Override
3.77 @@ -54,21 +93,454 @@
3.78 /** Creates new Cookie */
3.79 public Node.Cookie createCookie(Class klass) {
3.80 if (klass.isAssignableFrom(TplEditorSupport.class)) {
3.81 - return getHtmlEditorSupport();
3.82 + return getTplEditorSupport();
3.83 } else {
3.84 return null;
3.85 }
3.86 }
3.87
3.88 - private synchronized TplEditorSupport getHtmlEditorSupport() {
3.89 + private synchronized TplEditorSupport getTplEditorSupport() {
3.90 if (tplEditorSupport == null) {
3.91 tplEditorSupport = new TplEditorSupport(this);
3.92 }
3.93 return tplEditorSupport;
3.94 }
3.95 -
3.96 +
3.97 // Package accessibility for TplEditorSupport:
3.98 - public CookieSet getCookieSet0() {
3.99 + CookieSet getCookieSet0() {
3.100 return getCookieSet();
3.101 }
3.102 +
3.103 + /** Checks the file for UTF-16 marks and calls findEncoding with properly loaded document content then. */
3.104 + String getFileEncoding() {
3.105 + InputStream is = null;
3.106 + try {
3.107 + FileObject pf = getPrimaryFile();
3.108 + if(!pf.isValid()) {
3.109 + return null;
3.110 + }
3.111 + is = pf.getInputStream();
3.112 + return getFileEncoding(is);
3.113 + } catch (IOException ex) {
3.114 + LOG.log(Level.WARNING, null, ex);
3.115 + } finally {
3.116 + try {
3.117 + if (is != null) {
3.118 + is.close();
3.119 + }
3.120 + } catch (IOException ex) {
3.121 + LOG.log(Level.WARNING, null, ex);
3.122 + }
3.123 + }
3.124 + return null;
3.125 + }
3.126 +
3.127 + private String getFileEncoding(final InputStream in) throws IOException {
3.128 + //detect encoding from input stream
3.129 + String encoding = null;
3.130 + byte[] arr = new byte[4096];
3.131 + int len = in.read(arr);
3.132 + len = (len >= 0) ? len : 0;
3.133 + //check UTF-16 mark
3.134 + if (len > 1) {
3.135 + int mark = (arr[0]&0xff)*0x100+(arr[1]&0xff);
3.136 + if (mark == 0xfeff) {
3.137 + encoding = "UTF-16"; // NOI18N
3.138 + } else if(mark == 0xfffe) {
3.139 + encoding = "UTF-16LE";
3.140 + }
3.141 + }
3.142 + //try to read the file using some encodings
3.143 + String[] encodings = new String[]{encoding != null ? encoding : DEFAULT_ENCODING, "UTF-16LE", "UTF-16BE"};
3.144 + int i = 0;
3.145 + do {
3.146 + encoding = findEncoding(makeString(arr, 0, len, encodings[i++]));
3.147 + } while (encoding == null && i < encodings.length);
3.148 +
3.149 + if (encoding != null) {
3.150 + encoding = encoding.trim();
3.151 + }
3.152 + return encoding;
3.153 + }
3.154 +
3.155 + private String makeString(byte[] arr, int offset, int len, String encoding) throws UnsupportedEncodingException {
3.156 + return new String(arr, 0, len, encoding).toUpperCase();
3.157 + }
3.158 +
3.159 +
3.160 + /** Tries to guess the mime type from given input stream. Tries to find
3.161 + * <em><meta http-equiv="Content-Type" content="text/html; charset=iso-8859-1"></em>
3.162 + * @param txt the string to search in (should be in upper case)
3.163 + * @return the encoding or null if no has been found
3.164 + */
3.165 + static String findEncoding(String txt) {
3.166 + int[] offsets = findEncodingOffsets(txt);
3.167 + if (offsets.length == 3) {
3.168 + String encoding = txt.substring(offsets[0] + offsets[1], offsets[0] + offsets[2]);
3.169 + return encoding;
3.170 + }
3.171 + return null;
3.172 + }
3.173 +
3.174 + private static int[] findEncodingOffsets(String txt) {
3.175 + int[] rslt = new int[0];
3.176 + TokenHierarchy hi = TokenHierarchy.create(txt, HTMLTokenId.language());
3.177 + TokenSequence ts = hi.tokenSequence();
3.178 + ts.moveStart();
3.179 + while(ts.moveNext()) {
3.180 + Token token = ts.token();
3.181 + if(token.id() == HTMLTokenId.VALUE) {
3.182 + String tokenImage = token.text().toString();
3.183 + int charsetOffset = tokenImage.indexOf(CHARSET_DECL);
3.184 + charsetOffset = charsetOffset == -1 ? tokenImage.indexOf(CHARSET_DECL.toLowerCase()) : charsetOffset;
3.185 +
3.186 + int charsetEndOffset = charsetOffset + CHARSET_DECL.length();
3.187 + if (charsetOffset != -1){
3.188 + int endOffset = tokenImage.indexOf('"', charsetEndOffset);
3.189 +
3.190 + if (endOffset == -1){
3.191 + endOffset = tokenImage.indexOf('\'', charsetEndOffset);
3.192 + }
3.193 +
3.194 + if (endOffset == -1){
3.195 + endOffset = tokenImage.indexOf(';', charsetEndOffset);
3.196 + }
3.197 +
3.198 + if (endOffset == -1){
3.199 + return rslt;
3.200 + }
3.201 +
3.202 + rslt = new int[]{token.offset(hi), charsetEndOffset, endOffset};
3.203 + }
3.204 + }
3.205 + }
3.206 + return rslt;
3.207 + }
3.208 +
3.209 + private class FileEncodingQueryImpl extends FileEncodingQueryImplementation {
3.210 +
3.211 + private volatile Charset cachedEncoding;
3.212 + private final AtomicBoolean listeningOnContentChange = new AtomicBoolean();
3.213 + private final ThreadLocal<Boolean> callingFEQ = new ThreadLocal<Boolean>() {
3.214 + @Override
3.215 + protected Boolean initialValue() {
3.216 + return false;
3.217 + }
3.218 + };
3.219 +
3.220 + @Override
3.221 + public Charset getEncoding(FileObject file) {
3.222 + assert file != null;
3.223 + if(callingFEQ.get()) {
3.224 + //we are calling to the FEQ from within this method so
3.225 + //we must not return anything to prevent cycling
3.226 + return null;
3.227 + }
3.228 +
3.229 + Charset encoding = cachedEncoding;
3.230 + if (encoding != null) {
3.231 + LOG.log(Level.FINEST, "TplDataObject.getFileEncoding cached {0}", new Object[] {encoding}); //NOI18N
3.232 + return encoding;
3.233 + } else {
3.234 + //get the encoding from the FEQ excluding this FEQ implementation
3.235 + //so the proxy charset can default to appropriate encoding
3.236 + callingFEQ.set(true);
3.237 + try {
3.238 + Charset charset = FileEncodingQuery.getEncoding(file);
3.239 + return new ProxyCharset(charset);
3.240 + } finally {
3.241 + callingFEQ.set(false);
3.242 + }
3.243 + }
3.244 + }
3.245 +
3.246 + private Charset cache (final Charset encoding) {
3.247 +
3.248 + if (!listeningOnContentChange.getAndSet(true)) {
3.249 + final FileObject primaryFile = getPrimaryFile();
3.250 + primaryFile.addFileChangeListener(FileUtil.weakFileChangeListener(new FileChangeAdapter(){
3.251 + @Override
3.252 + public void fileChanged(FileEvent fe) {
3.253 + cachedEncoding = null;
3.254 + }
3.255 + },primaryFile));
3.256 + }
3.257 + cachedEncoding = encoding;
3.258 + LOG.log(Level.FINEST, "TplDataObject.getFileEncoding noncached {0}", new Object[] {encoding}); //NOI18N
3.259 + return encoding;
3.260 + }
3.261 +
3.262 +
3.263 + private class ProxyCharset extends Charset {
3.264 +
3.265 + public ProxyCharset (Charset charset) {
3.266 + super (charset.name(), new String[0]); //NOI18N
3.267 + }
3.268 +
3.269 + public boolean contains(Charset c) {
3.270 + return false;
3.271 + }
3.272 +
3.273 + public CharsetDecoder newDecoder() {
3.274 + return new HtmlDecoder (this);
3.275 + }
3.276 +
3.277 + public CharsetEncoder newEncoder() {
3.278 + return new HtmlEncoder (this);
3.279 + }
3.280 + }
3.281 +
3.282 + private class HtmlEncoder extends CharsetEncoder {
3.283 +
3.284 + private CharBuffer buffer = CharBuffer.allocate(4*1024);
3.285 + private CharBuffer remainder;
3.286 + private CharsetEncoder encoder;
3.287 +
3.288 + public HtmlEncoder (Charset cs) {
3.289 + super(cs, 1,2);
3.290 + }
3.291 +
3.292 +
3.293 + protected CoderResult encodeLoop(CharBuffer in, ByteBuffer out) {
3.294 + if (buffer == null) {
3.295 + assert encoder != null;
3.296 + if (remainder!=null) {
3.297 + CoderResult result = encoder.encode(remainder,out,false);
3.298 + if (!remainder.hasRemaining()) {
3.299 + remainder = null;
3.300 + }
3.301 + }
3.302 + CoderResult result = encoder.encode(in, out, false);
3.303 + return result;
3.304 + }
3.305 + if (buffer.remaining() == 0 || (buffer.position() > 0 && in.limit() == 0)) {
3.306 + return handleHead (in,out);
3.307 + }
3.308 + else if (buffer.remaining() < in.remaining()) {
3.309 + int limit = in.limit();
3.310 + in.limit(in.position()+buffer.remaining());
3.311 + buffer.put(in);
3.312 + in.limit(limit);
3.313 + return handleHead (in, out);
3.314 + }
3.315 + else {
3.316 + buffer.put(in);
3.317 + return CoderResult.UNDERFLOW;
3.318 + }
3.319 + }
3.320 +
3.321 + private CoderResult handleHead (CharBuffer in, ByteBuffer out) {
3.322 + String encoding = null;
3.323 + try {
3.324 + encoding = getEncoding ();
3.325 + } catch (IOException ioe) {
3.326 + Exceptions.printStackTrace(ioe);
3.327 + }
3.328 + if (encoding == null) {
3.329 + buffer = null;
3.330 + throwUnknownEncoding();
3.331 + return null;
3.332 + }
3.333 + else {
3.334 + Charset c;
3.335 + try {
3.336 + c = cache(Charset.forName(encoding));
3.337 + } catch (UnsupportedCharsetException e) {
3.338 + buffer = null;
3.339 + throwUnknownEncoding();
3.340 + return null;
3.341 + } catch (IllegalCharsetNameException e) {
3.342 + buffer = null;
3.343 + throwUnknownEncoding();
3.344 + return null;
3.345 + }
3.346 + encoder = c.newEncoder();
3.347 + return flushHead(in, out);
3.348 + }
3.349 + }
3.350 +
3.351 + private CoderResult flushHead (CharBuffer in , ByteBuffer out) {
3.352 + buffer.flip();
3.353 + CoderResult r = encoder.encode(buffer,out, in==null);
3.354 + if (r.isOverflow()) {
3.355 + remainder = buffer;
3.356 + buffer = null;
3.357 + return r;
3.358 + }
3.359 + else {
3.360 + buffer = null;
3.361 + if (in == null) {
3.362 + return r;
3.363 + }
3.364 + return encoder.encode(in, out, false);
3.365 + }
3.366 + }
3.367 +
3.368 + private String getEncoding () throws IOException {
3.369 + String text = buffer.asReadOnlyBuffer().flip().toString();
3.370 + InputStream in = new ByteArrayInputStream(text.getBytes());
3.371 + try {
3.372 + return getFileEncoding(in);
3.373 + } finally {
3.374 + in.close();
3.375 + }
3.376 + }
3.377 +
3.378 + @Override
3.379 + protected CoderResult implFlush(ByteBuffer out) {
3.380 + CoderResult res;
3.381 + if (buffer != null) {
3.382 + res = handleHead(null, out);
3.383 + return res;
3.384 + }
3.385 + else if (remainder != null) {
3.386 + encoder.encode(remainder, out, true);
3.387 + }
3.388 + else {
3.389 + CharBuffer empty = (CharBuffer) CharBuffer.allocate(0).flip();
3.390 + encoder.encode(empty, out, true);
3.391 + }
3.392 + res = encoder.flush(out);
3.393 + return res;
3.394 + }
3.395 +
3.396 + @Override
3.397 + protected void implReset() {
3.398 + if (encoder != null) {
3.399 + encoder.reset();
3.400 + }
3.401 + }
3.402 + }
3.403 +
3.404 + private class HtmlDecoder extends CharsetDecoder {
3.405 +
3.406 + private ByteBuffer buffer = ByteBuffer.allocate(4*1024);
3.407 + private ByteBuffer remainder;
3.408 + private CharsetDecoder decoder;
3.409 +
3.410 + public HtmlDecoder (Charset cs) {
3.411 + super (cs,1,2);
3.412 + }
3.413 +
3.414 + protected CoderResult decodeLoop(ByteBuffer in, CharBuffer out) {
3.415 + if (buffer == null) {
3.416 + assert decoder != null;
3.417 + if (remainder!=null) {
3.418 + ByteBuffer tmp = ByteBuffer.allocate(remainder.remaining() + in.remaining());
3.419 + tmp.put(remainder);
3.420 + tmp.put(in);
3.421 + tmp.flip();
3.422 + CoderResult result = decoder.decode(tmp,out,false);
3.423 + if (tmp.hasRemaining()) {
3.424 + remainder = tmp;
3.425 + }
3.426 + else {
3.427 + remainder = null;
3.428 + }
3.429 + return result;
3.430 + }
3.431 + else {
3.432 + return decoder.decode(in, out, false);
3.433 + }
3.434 + }
3.435 + if (buffer.remaining() == 0) {
3.436 + return handleHead (in,out);
3.437 + }
3.438 + else if (buffer.remaining() < in.remaining()) {
3.439 + int limit = in.limit();
3.440 + in.limit(in.position()+buffer.remaining());
3.441 + buffer.put(in);
3.442 + in.limit(limit);
3.443 + return handleHead (in, out);
3.444 + }
3.445 + else {
3.446 + buffer.put(in);
3.447 + return CoderResult.UNDERFLOW;
3.448 + }
3.449 + }
3.450 +
3.451 + private CoderResult handleHead (ByteBuffer in, CharBuffer out) {
3.452 + String encoding = null;
3.453 + try {
3.454 + encoding = getEncoding ();
3.455 + } catch (IOException ioe) {
3.456 + Exceptions.printStackTrace(ioe);
3.457 + }
3.458 + if (encoding == null) {
3.459 + buffer = null;
3.460 + throwUnknownEncoding();
3.461 + return null;
3.462 + }
3.463 + else {
3.464 + Charset c;
3.465 + try {
3.466 + c = cache(Charset.forName(encoding));
3.467 + } catch (UnsupportedCharsetException e) {
3.468 + buffer = null;
3.469 + throwUnknownEncoding();
3.470 + return null;
3.471 + } catch (IllegalCharsetNameException e) {
3.472 + buffer = null;
3.473 + throwUnknownEncoding();
3.474 + return null;
3.475 + }
3.476 + decoder = c.newDecoder();
3.477 + return flushHead(in, out);
3.478 + }
3.479 + }
3.480 +
3.481 + private CoderResult flushHead (ByteBuffer in , CharBuffer out) {
3.482 + buffer.flip();
3.483 + CoderResult r = decoder.decode(buffer,out, in==null);
3.484 + if (r.isOverflow()) {
3.485 + remainder = buffer;
3.486 + buffer = null;
3.487 + return r;
3.488 + }
3.489 + else {
3.490 + buffer = null;
3.491 + if (in == null) {
3.492 + return r;
3.493 + }
3.494 + return decoder.decode(in, out, false);
3.495 + }
3.496 + }
3.497 +
3.498 + private String getEncoding () throws IOException {
3.499 + byte[] arr = buffer.array();
3.500 + ByteArrayInputStream in = new ByteArrayInputStream (arr);
3.501 + try {
3.502 + return getFileEncoding(in);
3.503 + }
3.504 + finally {
3.505 + in.close();
3.506 + }
3.507 + }
3.508 +
3.509 + @Override
3.510 + protected CoderResult implFlush(CharBuffer out) {
3.511 + CoderResult res;
3.512 + if (buffer != null) {
3.513 + res = handleHead(null, out);
3.514 + return res;
3.515 + }
3.516 + else if (remainder != null) {
3.517 + decoder.decode(remainder, out, true);
3.518 + }
3.519 + else {
3.520 + ByteBuffer empty = (ByteBuffer) ByteBuffer.allocate(0).flip();
3.521 + decoder.decode(empty, out, true);
3.522 + }
3.523 + res = decoder.flush(out);
3.524 + return res;
3.525 + }
3.526 +
3.527 + @Override
3.528 + protected void implReset() {
3.529 + if (decoder != null) {
3.530 + decoder.reset();
3.531 + }
3.532 + }
3.533 + }
3.534 + }
3.535 }
4.1 --- a/php.smarty/src/org/netbeans/modules/php/smarty/editor/TplEditorSupport.java Fri Jul 08 10:53:41 2011 +0200
4.2 +++ b/php.smarty/src/org/netbeans/modules/php/smarty/editor/TplEditorSupport.java Sun Jul 17 01:29:31 2011 +0200
4.3 @@ -47,9 +47,15 @@
4.4 import java.io.OutputStreamWriter;
4.5 import java.io.Writer;
4.6 import java.nio.charset.Charset;
4.7 +import java.nio.charset.CharsetEncoder;
4.8 +import java.util.logging.Level;
4.9 +import java.util.logging.Logger;
4.10 import javax.swing.text.BadLocationException;
4.11 import javax.swing.text.EditorKit;
4.12 import javax.swing.text.StyledDocument;
4.13 +import org.netbeans.api.queries.FileEncodingQuery;
4.14 +import org.openide.DialogDisplayer;
4.15 +import org.openide.NotifyDescriptor;
4.16 import org.openide.cookies.EditCookie;
4.17 import org.openide.cookies.EditorCookie;
4.18 import org.openide.cookies.OpenCookie;
4.19 @@ -60,15 +66,23 @@
4.20 import org.openide.nodes.Node.Cookie;
4.21 import org.openide.text.CloneableEditor;
4.22 import org.openide.text.DataEditorSupport;
4.23 +import org.openide.util.NbBundle;
4.24 import org.openide.util.UserCancelException;
4.25 import org.openide.windows.CloneableOpenSupport;
4.26
4.27 /**
4.28 - * Editor support for TPL data objects.
4.29 - * @author Martin Fousek
4.30 + * Editor support for TPL data objects. Most code of this class was get from
4.31 + * HtmlEditorSupport - especially encoding support.
4.32 + *
4.33 + * @author Martin Fousek <marfous@netbeans.org>
4.34 */
4.35 public final class TplEditorSupport extends DataEditorSupport implements OpenCookie, EditCookie, EditorCookie.Observable, PrintCookie {
4.36
4.37 + private static final String DOCUMENT_SAVE_ENCODING = "Document_Save_Encoding";
4.38 + private static final String UTF_8_ENCODING = "UTF-8";
4.39 + // only to be ever user from unit tests:
4.40 + public static boolean showConfirmationDialog = true;
4.41 +
4.42 /** SaveCookie for this support instance. The cookie is adding/removing
4.43 * data object's cookie set depending on if modification flag was set/unset.
4.44 * It also invokes beforeSave() method on the TplDataObject to give it
4.45 @@ -100,16 +114,124 @@
4.46
4.47 @Override
4.48 public void saveDocument() throws IOException {
4.49 + updateEncoding();
4.50 super.saveDocument();
4.51 TplEditorSupport.this.getDataObject().setModified(false);
4.52 }
4.53 +
4.54 + void updateEncoding() throws UserCancelException {
4.55 + //try to find encoding specification in the editor content
4.56 + String documentContent = getDocumentText();
4.57 + String encoding = TplDataObject.findEncoding(documentContent);
4.58
4.59 - /**
4.60 +
4.61 +
4.62 +
4.63 +
4.64 + String feqEncoding = FileEncodingQuery.getEncoding(getDataObject().getPrimaryFile()).name();
4.65 + String finalEncoding = null;
4.66 + if (encoding != null) {
4.67 + //found encoding specified in the file content by meta tag
4.68 + if (!isSupportedEncoding(encoding) || !canEncode(documentContent, encoding)) {
4.69 + //test if the file can be saved by the original encoding or if it needs to be saved using utf-8
4.70 + finalEncoding = canEncode(documentContent, feqEncoding) ? feqEncoding : UTF_8_ENCODING;
4.71 + NotifyDescriptor nd = new NotifyDescriptor.Confirmation(NbBundle.getMessage(TplEditorSupport.class, "MSG_unsupportedEncodingSave", new Object[]{getDataObject().getPrimaryFile().getNameExt(), encoding, finalEncoding, finalEncoding.equals(UTF_8_ENCODING) ? "" : " the original"}), NotifyDescriptor.YES_NO_OPTION, NotifyDescriptor.WARNING_MESSAGE);
4.72 + nd.setValue(NotifyDescriptor.NO_OPTION);
4.73 + DialogDisplayer.getDefault().notify(nd);
4.74 + if (nd.getValue() != NotifyDescriptor.YES_OPTION) {
4.75 + throw new UserCancelException();
4.76 + }
4.77 + } else {
4.78 + finalEncoding = encoding;
4.79 + }
4.80 + } else {
4.81 + //no encoding specified in the file, use FEQ value
4.82 + if (!canEncode(documentContent, feqEncoding)) {
4.83 + NotifyDescriptor nd = new NotifyDescriptor.Confirmation(NbBundle.getMessage(TplEditorSupport.class, "MSG_badCharConversionSave", new Object[]{getDataObject().getPrimaryFile().getNameExt(), feqEncoding}), NotifyDescriptor.YES_NO_OPTION, NotifyDescriptor.WARNING_MESSAGE);
4.84 + nd.setValue(NotifyDescriptor.NO_OPTION);
4.85 + DialogDisplayer.getDefault().notify(nd);
4.86 + if (nd.getValue() != NotifyDescriptor.YES_OPTION) {
4.87 + throw new UserCancelException();
4.88 + } else {
4.89 + finalEncoding = UTF_8_ENCODING;
4.90 + }
4.91 + } else {
4.92 + finalEncoding = feqEncoding;
4.93 + }
4.94 + }
4.95 +
4.96 + //FEQ cannot be run in saveFromKitToStream since document is locked for writing,
4.97 + //so setting the FEQ result to document property
4.98 + getDocument().putProperty(DOCUMENT_SAVE_ENCODING, finalEncoding);
4.99 + }
4.100 +
4.101 +
4.102 + private String getDocumentText() {
4.103 + String text = "";
4.104 + try {
4.105 + StyledDocument doc = getDocument();
4.106 + if (doc != null) {
4.107 + text = doc.getText(doc.getStartPosition().getOffset(), doc.getLength());
4.108 + }
4.109 + } catch (BadLocationException e) {
4.110 + Logger.getLogger("global").log(Level.WARNING, null, e);
4.111 + }
4.112 + return text;
4.113 + }
4.114 +
4.115 +
4.116 + private boolean canEncode(String docText, String encoding) {
4.117 + CharsetEncoder encoder = Charset.forName(encoding).newEncoder();
4.118 + return encoder.canEncode(docText);
4.119 + }
4.120 +
4.121 + private boolean isSupportedEncoding(String encoding) {
4.122 + boolean supported;
4.123 + try {
4.124 + supported = java.nio.charset.Charset.isSupported(encoding);
4.125 + } catch (java.nio.charset.IllegalCharsetNameException e) {
4.126 + supported = false;
4.127 + }
4.128 + return supported;
4.129 + }
4.130 +
4.131 + @Override
4.132 + public void open() {
4.133 + String encoding = ((TplDataObject) getDataObject()).getFileEncoding();
4.134 + String feqEncoding = FileEncodingQuery.getEncoding(getDataObject().getPrimaryFile()).name();
4.135 + if (encoding != null && !isSupportedEncoding(encoding)) {
4.136 + if(!showConfirmationDialog) {
4.137 + return ; //simulate "No" pressed in the dialog if opened
4.138 + }
4.139 +// if(!canDecodeFile(getDataObject().getPrimaryFile(), feqEncoding)) {
4.140 +// feqEncoding = UTF_8_ENCODING;
4.141 +// }
4.142 + NotifyDescriptor nd = new NotifyDescriptor.Confirmation(
4.143 + NbBundle.getMessage(TplEditorSupport.class, "MSG_unsupportedEncodingLoad", //NOI18N
4.144 + new Object[]{getDataObject().getPrimaryFile().getNameExt(),
4.145 + encoding,
4.146 + feqEncoding}),
4.147 + NotifyDescriptor.YES_NO_OPTION,
4.148 + NotifyDescriptor.WARNING_MESSAGE);
4.149 + DialogDisplayer.getDefault().notify(nd);
4.150 + if (nd.getValue() != NotifyDescriptor.YES_OPTION) {
4.151 + return; // do not open the file
4.152 + }
4.153 + }
4.154 +
4.155 +// if(!canDecodeFile(getDataObject().getPrimaryFile(), feqEncoding)) {
4.156 +// feqEncoding = UTF_8_ENCODING;
4.157 +// }
4.158 +
4.159 + super.open();
4.160 + }
4.161 +
4.162 + /**
4.163 * @inheritDoc
4.164 */
4.165 @Override
4.166 protected void saveFromKitToStream(StyledDocument doc, EditorKit kit, OutputStream stream) throws IOException, BadLocationException {
4.167 - final Charset c = Charset.forName("UTF-8");
4.168 + final Charset c = Charset.forName((String) doc.getProperty(DOCUMENT_SAVE_ENCODING));
4.169 final Writer w = new OutputStreamWriter(stream, c);
4.170 try {
4.171 kit.write(w, doc, 0, doc.getLength());