package mergedoc.encoding.document; import static java.lang.String.*; import static org.eclipse.core.runtime.content.IContentDescription.*; import java.io.IOException; import java.io.InputStream; import java.nio.charset.Charset; import java.util.Arrays; import org.apache.commons.io.IOUtils; import org.apache.commons.lang3.ArrayUtils; import org.apache.commons.lang3.StringUtils; import org.eclipse.core.resources.IFile; import org.eclipse.core.resources.IProject; import org.eclipse.core.resources.IResourceChangeEvent; import org.eclipse.core.runtime.content.IContentDescription; import org.eclipse.jface.action.IStatusLineManager; import org.eclipse.jface.viewers.ISelection; import org.eclipse.ui.IEditorPart; import org.eclipse.ui.IWorkbenchPart; import org.eclipse.ui.PlatformUI; import org.eclipse.ui.editors.text.IEncodingSupport; import org.eclipse.ui.internal.WorkbenchWindow; import mergedoc.encoding.Activator; import mergedoc.encoding.Charsets; import mergedoc.encoding.IActiveDocumentAgentCallback; import mergedoc.encoding.JarResource; /** * This document handles editors which support IEncodingSupport for ActiveDocumentAgent. * @author Tsoi Yat Shing * @author Shinji Kashihara */ @SuppressWarnings("restriction") public class ActiveDocument { protected IActiveDocumentAgentCallback callback; protected IEditorPart editor; protected IEncodingSupport encodingSupport; protected String currentEncoding; protected String inheritedEncoding; protected String detectedCharset; protected String contentTypeEncoding; protected String contentCharset; protected byte[] bom; protected String lineSeparator; public ActiveDocument(IEditorPart editor, IActiveDocumentAgentCallback callback) { init(editor, callback); infoMessage(null); } protected void init(IEditorPart editor, IActiveDocumentAgentCallback callback) { this.editor = editor; this.callback = callback; if (editor == null) throw new IllegalArgumentException("editor must not be null."); if (callback == null) throw new IllegalArgumentException("callback must not be null."); this.encodingSupport = editor.getAdapter(IEncodingSupport.class); if (encodingSupport == null) throw new IllegalArgumentException("editor must provide IEncodingSupport."); updateStatus(); } public boolean canChangeEncoding() { return false; } public boolean canConvertContent() { return false; } public boolean enabledContentType() { return false; } /** * Get the editor associated with this handler. * If the associated editor is different from the active editor, ActiveDocumentAgent will change handler. */ public IEditorPart getEditor() { return editor; } public IProject getProject() { return null; } public JarResource getJarResource() { return null; } public IFile getFile() { return null; } public IContentDescription getContentDescription() { return null; } /** * Get the name of the active document, if supported by the editor and the editor input. * @return the name or null. */ public String getFileName() { return editor.getEditorInput().getName(); } public String getLineSeparator() { return lineSeparator; } /** * Get the encoding setting of the active document, if supported by the editor. * @return the encoding setting or null. */ public String getCurrentEncoding() { return currentEncoding; } public String getCurrentEncodingLabel() { if (currentEncoding == null) { return null; } StringBuilder sb = new StringBuilder(); sb.append(currentEncoding); if (canOperateBOM()) { if (bom != null) { if (currentEncoding.equals("UTF-16")) { if (bom == BOM_UTF_16BE) { sb.append(" BE"); } else if (bom == BOM_UTF_16LE) { sb.append(" LE"); } } if (currentEncoding.startsWith("UTF-")) { sb.append(" BOM"); } } } return sb.toString(); } public boolean canOperateBOM() { // Non support UTF-32 as in the Eclipse return currentEncoding != null && currentEncoding.matches("UTF-(8|16|16BE|16LE)"); } public boolean hasBOM() { return canOperateBOM() && bom != null; } public String getInheritedEncoding() { return inheritedEncoding; } public String getDetectedCharset() { return detectedCharset; } public String getContentTypeEncoding() { return contentTypeEncoding; } public String getContentCharset() { return contentCharset; } public boolean matchesEncoding() { return detectedCharset != null && Charsets.equals(detectedCharset, currentEncoding); } public boolean mismatchesEncoding() { return detectedCharset != null && !Charsets.equals(detectedCharset, currentEncoding); } public void propertyChanged(Object source, int propId) { // It seems that the editor's encoding will not change when it is dirty if (!editor.isDirty()) { // The document may be just saved. update(); } } public void resourceChanged(IResourceChangeEvent event) { // It seems that propertyChanged() can detect changes well already } public void selectionChanged(IWorkbenchPart part, ISelection selection) { // It seems that propertyChanged() can detect encoding setting changes well already } /** * Set the encoding of the active document, if supported by the editor. */ public void setEncoding(String encoding) { String contentCharset = null; IContentDescription contentDescription = getContentDescription(); if (contentDescription != null) { contentCharset = contentDescription.getCharset(); } if (contentCharset != null) { if (Charsets.equals(encoding, contentCharset)) { encoding = null; } } else if (Charsets.equals(encoding, inheritedEncoding)) { encoding = null; } try { // Null is clear for inheritance encodingSupport.setEncoding(encoding); } catch (Exception e) { // Ignore BackingStoreException for not sync project preferences store Activator.info("Failed set encoding", e); } refresh(); } public final void refresh() { warnMessage(null); update(); } protected final void update() { String currentEncodingOld = currentEncoding; String detectedCharsetOld = detectedCharset; String contentCharsetOld = contentCharset; byte[] bomOld = bom; String lineSeparatorOld = lineSeparator; updateStatus(); if ( !StringUtils.equals(currentEncodingOld, currentEncoding) || !StringUtils.equals(detectedCharsetOld, detectedCharset) || !StringUtils.equals(contentCharsetOld, contentCharset) || bomOld != bom || !StringUtils.equals(lineSeparatorOld, lineSeparator) ) { // Invoke the callback if the encoding information is changed callback.statusChanged(); } } /** * Update the encoding information in member variables. * This method may be overrided, but should be called by the sub-class. */ protected void updateStatus() { currentEncoding = null; inheritedEncoding = null; detectedCharset = null; contentTypeEncoding = null; bom = null; lineSeparator = null; if (encodingSupport != null) { currentEncoding = encodingSupport.getEncoding(); if (currentEncoding == null) { // workspace encoding currentEncoding = encodingSupport.getDefaultEncoding(); } bom = resolveBOM(); } } protected InputStream getInputStream() { throw new UnsupportedOperationException("Non implements getInputStream method."); } protected String getContentString() { InputStream inputStream = getInputStream(); try { return IOUtils.toString(inputStream, getCurrentEncoding()); } catch (IOException e) { throw new IllegalStateException(e); } finally { IOUtils.closeQuietly(inputStream); } } protected void setContents(byte[] bytes) { throw new UnsupportedOperationException("Non implements setContents method."); } protected void setContents(String content, String storeEncoding) { setContents(content.getBytes(Charset.forName(storeEncoding))); } protected byte[] resolveBOM() { IContentDescription cd = getContentDescription(); return (byte[]) (cd == null ? null : cd.getProperty(BYTE_ORDER_MARK)); } public void addBOM() { if (!hasBOM() && currentEncoding != null) { InputStream inputStream = getInputStream(); try { if (inputStream != null) { byte[] bytes = IOUtils.toByteArray(getInputStream()); byte[] newBytes = null; if (currentEncoding.equals("UTF-8")) { newBytes = ArrayUtils.addAll(BOM_UTF_8, bytes); } else if (currentEncoding.matches("UTF-16(|BE)")) { newBytes = ArrayUtils.addAll(BOM_UTF_16BE, bytes); } else if (currentEncoding.equals("UTF-16LE")) { newBytes = ArrayUtils.addAll(BOM_UTF_16LE, bytes); } else { // Not support UTF-32 as in the Eclipse throw new IllegalStateException("Encoding must be UTF-8 or UTF-16."); } setContents(newBytes); setEncoding(null); // Detemined Eclipse from BOM content } } catch (IOException e) { throw new IllegalStateException(e); } finally { IOUtils.closeQuietly(inputStream); } } } public void removeBOM() { InputStream inputStream = getInputStream(); try { byte[] bytes = IOUtils.toByteArray(getInputStream()); byte[] head3 = ArrayUtils.subarray(bytes, 0, BOM_UTF_8.length); if (Arrays.equals(head3, BOM_UTF_8)) { setContents(ArrayUtils.subarray(bytes, BOM_UTF_8.length, Integer.MAX_VALUE)); setEncoding("UTF-8"); // null if default } else { byte[] head2 = ArrayUtils.subarray(bytes, 0, BOM_UTF_16BE.length); if (Arrays.equals(head2, BOM_UTF_16BE)) { setContents(ArrayUtils.subarray(bytes, BOM_UTF_16BE.length, Integer.MAX_VALUE)); setEncoding("UTF-16BE"); } else if (Arrays.equals(head2, BOM_UTF_16LE)) { setContents(ArrayUtils.subarray(bytes, BOM_UTF_16BE.length, Integer.MAX_VALUE)); setEncoding("UTF-16LE"); } // Not support UTF-32 as in the Eclipse } } catch (IOException e) { throw new IllegalStateException(e); } finally { IOUtils.closeQuietly(inputStream); } } public void convertCharset(String newEncoding) { if (hasBOM()) { removeBOM(); } String content = getContentString(); setContents(content, newEncoding); setEncoding(newEncoding); } public void setLineSeparator(String newLineSeparator) { if (newLineSeparator.equals(lineSeparator)) { return; } String newSeparator = "\r\n"; if (newLineSeparator.equals("CR")) { newSeparator = "\r"; } else if (newLineSeparator.equals("LF")) { newSeparator = "\n"; } String content = getContentString().replaceAll("(\\r\\n|\\r|\\n)", newSeparator); setContents(content, getCurrentEncoding()); update(); } public void infoMessage(String message, Object... args) { setMessage("info", message, args); } public void warnMessage(String message, Object... args) { setMessage("warn", message, args); } public void warnDirtyMessage(boolean showsWarn) { if (showsWarn) { warnMessage("Editor must be saved before status bar action."); } } private void setMessage(String imageIconKey, String message, Object... args) { IStatusLineManager statusLineManager = null; if (editor == null) { WorkbenchWindow window = (WorkbenchWindow) PlatformUI.getWorkbench().getActiveWorkbenchWindow(); statusLineManager = window.getActionBars().getStatusLineManager(); } else { statusLineManager = editor.getEditorSite().getActionBars().getStatusLineManager(); } if (statusLineManager != null) { if (message == null) { statusLineManager.setMessage(null); } else { statusLineManager.setMessage(Activator.getImage(imageIconKey), format(message, args)); } } } }