--- a/openide.io/apichanges.xml +++ a/openide.io/apichanges.xml @@ -107,6 +107,30 @@ + + + Adding API to have support for folding of lines + + + + + +

Adding class IOFolding for checking that folding is supported and + for creating folds in output window, and class FoldHandle for + finishing the folds and creation of nested folds.

+

See + + + IOFolding, + + FoldHandle. + +

+
+ + + +
Added IOProvider.getIO variant that takes all 4 possible parameters. --- a/openide.io/manifest.mf +++ a/openide.io/manifest.mf @@ -1,6 +1,6 @@ Manifest-Version: 1.0 OpenIDE-Module: org.openide.io -OpenIDE-Module-Specification-Version: 1.37 +OpenIDE-Module-Specification-Version: 1.38 OpenIDE-Module-Localizing-Bundle: org/openide/io/Bundle.properties OpenIDE-Module-Recommends: org.openide.windows.IOProvider, org.openide.windows.IOContainer$Provider AutoUpdate-Essential-Module: true --- a/openide.io/nbproject/project.xml +++ a/openide.io/nbproject/project.xml @@ -46,7 +46,7 @@ org.netbeans.modules.apisupport.project - + org.openide.io @@ -75,6 +75,15 @@ + + + unit + + org.netbeans.libs.junit4 + + + + org.openide.windows --- a/openide.io/src/org/openide/windows/FoldHandle.java +++ a/openide.io/src/org/openide/windows/FoldHandle.java @@ -0,0 +1,91 @@ +/* + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER. + * + * Copyright 2013 Oracle and/or its affiliates. All rights reserved. + * + * Oracle and Java are registered trademarks of Oracle and/or its affiliates. + * Other names may be trademarks of their respective owners. + * + * The contents of this file are subject to the terms of either the GNU + * General Public License Version 2 only ("GPL") or the Common + * Development and Distribution License("CDDL") (collectively, the + * "License"). You may not use this file except in compliance with the + * License. You can obtain a copy of the License at + * http://www.netbeans.org/cddl-gplv2.html + * or nbbuild/licenses/CDDL-GPL-2-CP. See the License for the + * specific language governing permissions and limitations under the + * License. When distributing the software, include this License Header + * Notice in each file and include the License file at + * nbbuild/licenses/CDDL-GPL-2-CP. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the GPL Version 2 section of the License file that + * accompanied this code. If applicable, add the following below the + * License Header, with the fields enclosed by brackets [] replaced by + * your own identifying information: + * "Portions Copyrighted [year] [name of copyright owner]" + * + * If you wish your version of this file to be governed by only the CDDL + * or only the GPL Version 2, indicate your decision by adding + * "[Contributor] elects to include this software in this distribution + * under the [CDDL or GPL Version 2] license." If you do not indicate a + * single choice of license, a recipient has the option to distribute + * your version of this file under either the CDDL, the GPL Version 2 or + * to extend the choice of license to its licensees as provided above. + * However, if you add GPL Version 2 code and therefore, elected the GPL + * Version 2 license, then the option applies only if the new code is + * made subject to such option by the copyright holder. + * + * Contributor(s): + * + * Portions Copyrighted 2013 Sun Microsystems, Inc. + */ + +package org.openide.windows; + +import org.openide.windows.IOFolding.FoldHandleDefinition; + +/** + * An object that refers to a fold in output window. It can be used to finish + * the fold, or to create nested folds. + * + * @author jhavlin + * @since openide.io/1.38 + */ +public final class FoldHandle { + + private final FoldHandleDefinition definition; + + FoldHandle(FoldHandleDefinition definition) { + this.definition = definition; + } + + /** + * Finish the fold at the current last line in the output window. + * + * @throws IllegalStateException if parent fold has been already finished, + * or if there is an unfinished child fold. + */ + public void finish() { + definition.finish(); + } + + /** + * Start a nested fold at the current last line in output window. + * + * @param expanded True to expand the new fold, false to collapse it. + * @return Handle for the newly created fold. + * @throws IllegalStateException if the fold has been already finished. + */ + public FoldHandle startFold(boolean expanded) { + return new FoldHandle(definition.startFold(expanded)); + } + + /** + * Set state of the fold. + * + * @param expanded True to expand the fold, false to collapse it. + */ + public void setExpanded(boolean expanded) { + definition.setExpanded(expanded); + } +} --- a/openide.io/src/org/openide/windows/IOFolding.java +++ a/openide.io/src/org/openide/windows/IOFolding.java @@ -0,0 +1,183 @@ +/* + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER. + * + * Copyright 2013 Oracle and/or its affiliates. All rights reserved. + * + * Oracle and Java are registered trademarks of Oracle and/or its affiliates. + * Other names may be trademarks of their respective owners. + * + * The contents of this file are subject to the terms of either the GNU + * General Public License Version 2 only ("GPL") or the Common + * Development and Distribution License("CDDL") (collectively, the + * "License"). You may not use this file except in compliance with the + * License. You can obtain a copy of the License at + * http://www.netbeans.org/cddl-gplv2.html + * or nbbuild/licenses/CDDL-GPL-2-CP. See the License for the + * specific language governing permissions and limitations under the + * License. When distributing the software, include this License Header + * Notice in each file and include the License file at + * nbbuild/licenses/CDDL-GPL-2-CP. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the GPL Version 2 section of the License file that + * accompanied this code. If applicable, add the following below the + * License Header, with the fields enclosed by brackets [] replaced by + * your own identifying information: + * "Portions Copyrighted [year] [name of copyright owner]" + * + * If you wish your version of this file to be governed by only the CDDL + * or only the GPL Version 2, indicate your decision by adding + * "[Contributor] elects to include this software in this distribution + * under the [CDDL or GPL Version 2] license." If you do not indicate a + * single choice of license, a recipient has the option to distribute + * your version of this file under either the CDDL, the GPL Version 2 or + * to extend the choice of license to its licensees as provided above. + * However, if you add GPL Version 2 code and therefore, elected the GPL + * Version 2 license, then the option applies only if the new code is + * made subject to such option by the copyright holder. + * + * Contributor(s): + * + * Portions Copyrighted 2013 Sun Microsystems, Inc. + */ +package org.openide.windows; + +import org.netbeans.api.annotations.common.CheckReturnValue; +import org.netbeans.api.annotations.common.NonNull; +import org.openide.util.Lookup; +import org.openide.util.Lookup.Provider; +import org.openide.util.Parameters; + +/** + * Folding of group of lines in Output Window. + *

+ * Client usage: + *

+ *
+ *  InputOutput io = ...;
+ *  if (!IOFolding.isSupported(io)) {
+ *    throw new Exception("Folding is not supported");
+ *  }
+ *  io.getOut().println("First Line - start of fold");
+ *  FoldHandle fold = IOFolding.startFold(io, true);
+ *  io.getOut().println("  Fold Content 1");
+ *  io.getOut().println("  The first line of nested fold");
+ *  FoldHandle nestedFold = fold.startFold(true);
+ *  io.getOut().println("     Nested fold content 1");
+ *  nestedFold.finish();
+ *  io.getOut().println("  Fold Content 2");
+ *  fold.finish();
+ *  io.getOut().println("Text outside of the fold.");
+ * 
+ *

+ * How to support {@link IOFolding} in own {@link IOProvider} implementation: + *

+ *
    + *
  • {@link InputOutput} provided by {@link IOProvider} has to implement + * {@link Provider}
  • + *
  • Extend {@link IOFolding} and implement its abstract methods
  • + *
  • Extend {@link FoldHandleDefinition}
  • + *
  • Place instance of {@link IOFolding} to {@link Lookup} provided by + * {@link InputOutput}
  • + *
+ * + * @author jhavlin + * @since openide.io/1.38 + */ +public abstract class IOFolding { + + /** + * Check whether an {@link InputOutput} supports folding. + * + * @param io The InputOutput to check. + * @return True if {@link #startFold(InputOutput, boolean)} can be used with + * {@code io}, false otherwise. + */ + public static boolean isSupported(@NonNull InputOutput io) { + Parameters.notNull("parent", io); //NOI18N + return findIOFolding(io) != null; + } + + /** + * Find folding support in an {@link InputOutput} object. + * + * @return IOFolding object if folding is supported by the {@code parent} + * object, null otherwise. + */ + private static IOFolding findIOFolding(InputOutput parent) { + return (parent instanceof Lookup.Provider) + ? ((Lookup.Provider) parent).getLookup().lookup(IOFolding.class) + : null; + } + + /** + * Create a fold handle definition for the current last line in the output + * window. + * + * @param expanded Initial state of the fold. + * @return FoldHandleDefinition for the fold handle. Never null. + * + * @throws IllegalStateException if the last fold hasn't been finished yet. + */ + @NonNull + protected abstract FoldHandleDefinition startFold(boolean expanded); + + /** + * Create a fold handle for the current last line in the output window. + * + * @param io InputOutput to create the fold in. + * @param expanded Initial state of the fold. + * @return The fold handle that can be used to finish the fold or to create + * nested folds. + * @throws IllegalStateException if the last fold hasn't been finished yet. + * @throws UnsupportedOperationException if folding is not supported by the + * InputOutput object. + */ + @CheckReturnValue + @NonNull + public static FoldHandle startFold( + @NonNull InputOutput io, boolean expanded) { + + Parameters.notNull("io", io); //NOI18N + IOFolding folding = findIOFolding(io); + if (folding == null) { + throw new UnsupportedOperationException( + "The InputOutput doesn't support folding"); //NOI18N + } else { + return new FoldHandle(folding.startFold(expanded)); + } + } + + /** + * An SPI for creating custom FoldHandle implementations. + */ + protected static abstract class FoldHandleDefinition { + + /** + * Finish the fold at the current last line. Ensure that nested folds + * are finished correctly. + * + * @throws IllegalStateException if parent fold has been already + * finished, or if there is an unfinished nested fold. + */ + public abstract void finish(); + + /** + * Start a new fold at the current last line. Ensure that the parent + * fold hasn't been finished yet. + * + * @param expanded If false, the fold will be collapsed by default, + * otherwise it will be expanded. + * @return FoldHandleDefinition of handle for the newly created fold. + * @throws IllegalStateException if the fold has been already finished, + * or if the last nested fold hasn't been finished yet. + */ + public abstract FoldHandleDefinition startFold(boolean expanded); + + /** + * Set state of the fold. + * + * @param expanded True to expand the fold, false to collapse it. + */ + public abstract void setExpanded(boolean expanded); + } +} --- a/openide.io/test/unit/src/org/openide/windows/IOFoldingTest.java +++ a/openide.io/test/unit/src/org/openide/windows/IOFoldingTest.java @@ -0,0 +1,243 @@ +/* + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER. + * + * Copyright 2013 Oracle and/or its affiliates. All rights reserved. + * + * Oracle and Java are registered trademarks of Oracle and/or its affiliates. + * Other names may be trademarks of their respective owners. + * + * The contents of this file are subject to the terms of either the GNU + * General Public License Version 2 only ("GPL") or the Common + * Development and Distribution License("CDDL") (collectively, the + * "License"). You may not use this file except in compliance with the + * License. You can obtain a copy of the License at + * http://www.netbeans.org/cddl-gplv2.html + * or nbbuild/licenses/CDDL-GPL-2-CP. See the License for the + * specific language governing permissions and limitations under the + * License. When distributing the software, include this License Header + * Notice in each file and include the License file at + * nbbuild/licenses/CDDL-GPL-2-CP. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the GPL Version 2 section of the License file that + * accompanied this code. If applicable, add the following below the + * License Header, with the fields enclosed by brackets [] replaced by + * your own identifying information: + * "Portions Copyrighted [year] [name of copyright owner]" + * + * If you wish your version of this file to be governed by only the CDDL + * or only the GPL Version 2, indicate your decision by adding + * "[Contributor] elects to include this software in this distribution + * under the [CDDL or GPL Version 2] license." If you do not indicate a + * single choice of license, a recipient has the option to distribute + * your version of this file under either the CDDL, the GPL Version 2 or + * to extend the choice of license to its licensees as provided above. + * However, if you add GPL Version 2 code and therefore, elected the GPL + * Version 2 license, then the option applies only if the new code is + * made subject to such option by the copyright holder. + * + * Contributor(s): + * + * Portions Copyrighted 2013 Sun Microsystems, Inc. + */ +package org.openide.windows; + +import java.io.Reader; +import static org.junit.Assert.*; +import org.junit.Test; +import org.openide.util.Lookup; +import org.openide.util.lookup.Lookups; + +/** + * + * @author jhavlin + */ +public class IOFoldingTest { + + @Test + public void testStartFold() { + InputOutputWithFolding io = new InputOutputWithFolding(); + FoldHandle fold1 = IOFolding.startFold(io, true); + assertEquals(1, io.currentLevel); + FoldHandle fold2 = fold1.startFold(true); + assertEquals(2, io.currentLevel); + fold2.finish(); + assertEquals(1, io.currentLevel); + fold1.finish(); + assertEquals(0, io.currentLevel); + } + + @Test(expected = UnsupportedOperationException.class) + public void testStartFoldedUnsupported() { + FoldHandle f = IOFolding.startFold(new DummyInputOutput(), true); + } + + @Test(expected = IllegalStateException.class) + public void testStartFoldIllegal() { + InputOutputWithFolding io = new InputOutputWithFolding(); + FoldHandle f1 = IOFolding.startFold(io, true); + FoldHandle f2 = IOFolding.startFold(io, true); + } + + @Test(expected = IllegalStateException.class) + public void testFinishIllegal() { + InputOutputWithFolding io = new InputOutputWithFolding(); + FoldHandle f1 = IOFolding.startFold(io, true); + FoldHandle f2 = f1.startFold(true); + f1.finish(); + } + + @Test + public void testIsSupported() { + assertTrue(IOFolding.isSupported(new InputOutputWithFolding())); + } + + @Test + public void testIsSupportedUnsupported() { + assertFalse(IOFolding.isSupported(new DummyInputOutput())); + } + + /** + * Dummy InputOutput that supports folding. + */ + private static class InputOutputWithFolding extends DummyInputOutput + implements Lookup.Provider { + + private final Lookup lookup = Lookups.fixed(new DummyIOFolding()); + + private DummyIOFolding.DummyFoldHandleDef currentFold = null; + private int currentLevel = 0; + + @Override + public Lookup getLookup() { + return lookup; + } + + private class DummyIOFolding extends IOFolding { + + @Override + protected FoldHandleDefinition startFold(boolean expanded) { + if (currentFold != null) { + throw new IllegalStateException("Last fold not finished"); + } else { + currentFold = new DummyFoldHandleDef(null); + currentLevel++; + return currentFold; + } + } + + private class DummyFoldHandleDef extends FoldHandleDefinition { + + private DummyFoldHandleDef nested = null; + private final DummyFoldHandleDef parent; + + public DummyFoldHandleDef(DummyFoldHandleDef parent) { + this.parent = parent; + } + + @Override + public void finish() { + if (nested != null) { + throw new IllegalStateException("A nested fold exists."); + } else if (parent == null) { + currentFold = null; + } else { + parent.nested = null; + } + currentLevel--; + } + + @Override + public FoldHandleDefinition startFold(boolean expanded) { + if (nested != null) { + throw new IllegalStateException("Last fold not finished."); + } else { + nested = new DummyFoldHandleDef(this); + currentLevel++; + return nested; + } + } + + @Override + public void setExpanded(boolean expanded) { + } + } + } + } + + /** + * Dummy implementation of InputOutput. + */ + private static class DummyInputOutput implements InputOutput { + + @Override + public OutputWriter getOut() { + throw new UnsupportedOperationException("Not supported yet."); + } + + @Override + public Reader getIn() { + throw new UnsupportedOperationException("Not supported yet."); + } + + @Override + public OutputWriter getErr() { + throw new UnsupportedOperationException("Not supported yet."); + } + + @Override + public void closeInputOutput() { + throw new UnsupportedOperationException("Not supported yet."); + } + + @Override + public boolean isClosed() { + throw new UnsupportedOperationException("Not supported yet."); + } + + @Override + public void setOutputVisible(boolean value) { + throw new UnsupportedOperationException("Not supported yet."); + } + + @Override + public void setErrVisible(boolean value) { + throw new UnsupportedOperationException("Not supported yet."); + } + + @Override + public void setInputVisible(boolean value) { + throw new UnsupportedOperationException("Not supported yet."); + } + + @Override + public void select() { + throw new UnsupportedOperationException("Not supported yet."); + } + + @Override + public boolean isErrSeparated() { + throw new UnsupportedOperationException("Not supported yet."); + } + + @Override + public void setErrSeparated(boolean value) { + throw new UnsupportedOperationException("Not supported yet."); + } + + @Override + public boolean isFocusTaken() { + throw new UnsupportedOperationException("Not supported yet."); + } + + @Override + public void setFocusTaken(boolean value) { + throw new UnsupportedOperationException("Not supported yet."); + } + + @Override + @Deprecated + public Reader flushReader() { + throw new UnsupportedOperationException("Not supported yet."); + } + } +}