Merge branch release/0.4.0 into master

Upgrade kemitix-parent to 2.0.0
Add ImmutableTree implementation
Switch to static factory constructors
This commit is contained in:
Paul Campbell 2016-09-13 07:39:05 +01:00
commit f4d1d6e689
26 changed files with 1157 additions and 1301 deletions

36
.gitignore vendored
View file

@ -1,10 +1,3 @@
/target
/nbproject
*.class
# Mobile Tools for Java (J2ME)
.mtj.tmp/
# Package Files #
*.jar
*.war
@ -13,5 +6,30 @@
# virtual machine crash logs, see http://www.java.com/en/download/help/error_hotspot.xml
hs_err_pid*
/.idea/libraries/
/.idea/workspace.xml
# maven build outputs
target/
# netbeans legacy
nbproject/
nbactions.xml
# eclipse legacy
.project
# intellij
.idea/
*.iml
# Spring
spring.log
logs/
/application.properties
/bootstrap.properties
# Composer-style
vendor
# Git and temp files
*.orig
*.patch
*~

View file

@ -1,10 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="CheckStyle-IDEA">
<option name="configuration">
<map>
<entry key="scan-before-checkin" value="false" />
</map>
</option>
</component>
</project>

View file

@ -1,104 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="ProjectCodeStyleSettingsManager">
<option name="PER_PROJECT_SETTINGS">
<value>
<option name="GENERATE_FINAL_LOCALS" value="true" />
<option name="GENERATE_FINAL_PARAMETERS" value="true" />
<option name="VISIBILITY" value="packageLocal" />
<option name="CLASS_COUNT_TO_USE_IMPORT_ON_DEMAND" value="9999" />
<option name="NAMES_COUNT_TO_USE_IMPORT_ON_DEMAND" value="9999" />
<option name="PACKAGES_TO_USE_IMPORT_ON_DEMAND">
<value />
</option>
<option name="IMPORT_LAYOUT_TABLE">
<value>
<package name="" withSubpackages="true" static="false" />
<emptyLine />
<package name="" withSubpackages="true" static="true" />
<emptyLine />
<package name="java" withSubpackages="true" static="false" />
<emptyLine />
<package name="javax" withSubpackages="true" static="false" />
<emptyLine />
<package name="org.springframework" withSubpackages="true" static="false" />
<emptyLine />
<package name="uk.ac.fife" withSubpackages="true" static="false" />
<emptyLine />
<package name="net.kemitix" withSubpackages="true" static="false" />
</value>
</option>
<option name="JD_ADD_BLANK_AFTER_PARM_COMMENTS" value="true" />
<option name="JD_ADD_BLANK_AFTER_RETURN" value="true" />
<option name="WRAP_COMMENTS" value="true" />
<JavaCodeStyleSettings>
<option name="ANNOTATION_PARAMETER_WRAP" value="1" />
</JavaCodeStyleSettings>
<XML>
<option name="XML_LEGACY_SETTINGS_IMPORTED" value="true" />
</XML>
<codeStyleSettings language="JAVA">
<option name="RIGHT_MARGIN" value="80" />
<option name="KEEP_LINE_BREAKS" value="false" />
<option name="KEEP_FIRST_COLUMN_COMMENT" value="false" />
<option name="KEEP_CONTROL_STATEMENT_IN_ONE_LINE" value="false" />
<option name="KEEP_BLANK_LINES_IN_DECLARATIONS" value="1" />
<option name="KEEP_BLANK_LINES_IN_CODE" value="1" />
<option name="KEEP_BLANK_LINES_BEFORE_RBRACE" value="1" />
<option name="BLANK_LINES_AROUND_FIELD" value="1" />
<option name="BLANK_LINES_AROUND_FIELD_IN_INTERFACE" value="1" />
<option name="BLANK_LINES_AFTER_CLASS_HEADER" value="1" />
<option name="INDENT_CASE_FROM_SWITCH" value="false" />
<option name="ALIGN_MULTILINE_CHAINED_METHODS" value="true" />
<option name="ALIGN_MULTILINE_THROWS_LIST" value="true" />
<option name="SPACE_BEFORE_ARRAY_INITIALIZER_LBRACE" value="true" />
<option name="SPACE_BEFORE_ANNOTATION_ARRAY_INITIALIZER_LBRACE" value="true" />
<option name="CALL_PARAMETERS_WRAP" value="1" />
<option name="METHOD_PARAMETERS_WRAP" value="1" />
<option name="METHOD_PARAMETERS_LPAREN_ON_NEXT_LINE" value="true" />
<option name="RESOURCE_LIST_WRAP" value="1" />
<option name="EXTENDS_LIST_WRAP" value="1" />
<option name="THROWS_LIST_WRAP" value="1" />
<option name="EXTENDS_KEYWORD_WRAP" value="1" />
<option name="THROWS_KEYWORD_WRAP" value="1" />
<option name="METHOD_CALL_CHAIN_WRAP" value="5" />
<option name="BINARY_OPERATION_WRAP" value="1" />
<option name="BINARY_OPERATION_SIGN_ON_NEXT_LINE" value="true" />
<option name="TERNARY_OPERATION_WRAP" value="5" />
<option name="TERNARY_OPERATION_SIGNS_ON_NEXT_LINE" value="true" />
<option name="FOR_STATEMENT_WRAP" value="1" />
<option name="ARRAY_INITIALIZER_WRAP" value="1" />
<option name="ASSIGNMENT_WRAP" value="1" />
<option name="PLACE_ASSIGNMENT_SIGN_ON_NEXT_LINE" value="true" />
<option name="ASSERT_STATEMENT_WRAP" value="1" />
<option name="ASSERT_STATEMENT_COLON_ON_NEXT_LINE" value="true" />
<option name="IF_BRACE_FORCE" value="3" />
<option name="DOWHILE_BRACE_FORCE" value="3" />
<option name="WHILE_BRACE_FORCE" value="3" />
<option name="FOR_BRACE_FORCE" value="3" />
<option name="WRAP_LONG_LINES" value="true" />
<option name="PARAMETER_ANNOTATION_WRAP" value="1" />
<option name="VARIABLE_ANNOTATION_WRAP" value="1" />
<option name="ENUM_CONSTANTS_WRAP" value="1" />
<arrangement>
<groups>
<group>
<type>GETTERS_AND_SETTERS</type>
<order>KEEP</order>
</group>
<group>
<type>OVERRIDDEN_METHODS</type>
<order>KEEP</order>
</group>
<group>
<type>DEPENDENT_METHODS</type>
<order>BREADTH_FIRST</order>
</group>
</groups>
</arrangement>
</codeStyleSettings>
</value>
</option>
<option name="USE_PER_PROJECT_SETTINGS" value="true" />
</component>
</project>

View file

@ -1,33 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="CompilerConfiguration">
<option name="DEFAULT_COMPILER" value="Javac" />
<resourceExtensions />
<wildcardResourcePatterns>
<entry name="!?*.java" />
<entry name="!?*.form" />
<entry name="!?*.class" />
<entry name="!?*.groovy" />
<entry name="!?*.scala" />
<entry name="!?*.flex" />
<entry name="!?*.kt" />
<entry name="!?*.clj" />
<entry name="!?*.aj" />
</wildcardResourcePatterns>
<annotationProcessing>
<profile default="true" name="Default" enabled="false">
<processorPath useClasspath="true" />
</profile>
<profile default="false" name="Maven default annotation processors profile" enabled="true">
<sourceOutputDir name="target/generated-sources/annotations" />
<sourceTestOutputDir name="target/generated-test-sources/test-annotations" />
<outputRelativeToContentRoot value="true" />
<processorPath useClasspath="true" />
<module name="node" />
</profile>
</annotationProcessing>
<bytecodeTargetLevel>
<module name="node" target="1.8" />
</bytecodeTargetLevel>
</component>
</project>

View file

@ -1,7 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="Encoding">
<file url="file://$PROJECT_DIR$" charset="UTF-8" />
<file url="PROJECT" charset="UTF-8" />
</component>
</project>

View file

@ -1,213 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="org.twodividedbyzero.idea.findbugs">
<option name="annotationTypeSettings">
<map>
<entry key="ExpPriority" value="-4473925;-12828863;-8355712;WAVE_UNDERSCORE;0;" />
<entry key="HighPriority" value="-39836;-12828863;-39836;WAVE_UNDERSCORE;1;" />
<entry key="IgnorePriority" value="-4473925;-12828863;-11978414;WAVE_UNDERSCORE;0;" />
<entry key="LowPriority" value="-4473925;-12828863;-10316203;BOXED;0;" />
<entry key="NormalPriority" value="-4473925;-12828863;-10461184;WAVE_UNDERSCORE;2;" />
</map>
</option>
<option name="_basePreferences">
<map>
<entry key="property.analysisEffortLevel" value="default" />
<entry key="property.analyzeAfterCompile" value="false" />
<entry key="property.annotationGutterIconEnabled" value="true" />
<entry key="property.annotationSuppressWarningsClass" value="edu.umd.cs.findbugs.annotations.SuppressWarnings" />
<entry key="property.annotationTextRangeMarkupEnabled" value="true" />
<entry key="property.exportAsHtml" value="true" />
<entry key="property.exportAsXml" value="true" />
<entry key="property.exportBaseDir" value="" />
<entry key="property.exportCreateArchiveDir" value="false" />
<entry key="property.exportOpenBrowser" value="true" />
<entry key="property.minPriorityToReport" value="Medium" />
<entry key="property.runAnalysisInBackground" value="false" />
<entry key="property.showHiddenDetectors" value="false" />
<entry key="property.toolWindowToFront" value="true" />
</map>
</option>
<option name="_detectors">
<map>
<entry key="AppendingToAnObjectOutputStream" value="true" />
<entry key="AtomicityProblem" value="true" />
<entry key="BadAppletConstructor" value="false" />
<entry key="BadResultSetAccess" value="true" />
<entry key="BadSyntaxForRegularExpression" value="true" />
<entry key="BadUseOfReturnValue" value="true" />
<entry key="BadlyOverriddenAdapter" value="true" />
<entry key="BooleanReturnNull" value="true" />
<entry key="BuildInterproceduralCallGraph" value="false" />
<entry key="BuildObligationPolicyDatabase" value="true" />
<entry key="CallToUnsupportedMethod" value="false" />
<entry key="CalledMethods" value="true" />
<entry key="CheckCalls" value="false" />
<entry key="CheckExpectedWarnings" value="false" />
<entry key="CheckImmutableAnnotation" value="true" />
<entry key="CheckTypeQualifiers" value="true" />
<entry key="CloneIdiom" value="true" />
<entry key="ComparatorIdiom" value="true" />
<entry key="ConfusedInheritance" value="true" />
<entry key="ConfusionBetweenInheritedAndOuterMethod" value="true" />
<entry key="CrossSiteScripting" value="true" />
<entry key="DefaultEncodingDetector" value="true" />
<entry key="DoInsideDoPrivileged" value="true" />
<entry key="DontCatchIllegalMonitorStateException" value="true" />
<entry key="DontIgnoreResultOfPutIfAbsent" value="true" />
<entry key="DontUseEnum" value="true" />
<entry key="DroppedException" value="true" />
<entry key="DumbMethodInvocations" value="true" />
<entry key="DumbMethods" value="true" />
<entry key="DuplicateBranches" value="true" />
<entry key="EmptyZipFileEntry" value="true" />
<entry key="EqualsOperandShouldHaveClassCompatibleWithThis" value="true" />
<entry key="ExplicitSerialization" value="true" />
<entry key="FieldItemSummary" value="true" />
<entry key="FinalizerNullsFields" value="true" />
<entry key="FindBadCast2" value="true" />
<entry key="FindBadForLoop" value="true" />
<entry key="FindBugsSummaryStats" value="true" />
<entry key="FindCircularDependencies" value="false" />
<entry key="FindDeadLocalStores" value="true" />
<entry key="FindDoubleCheck" value="true" />
<entry key="FindEmptySynchronizedBlock" value="true" />
<entry key="FindFieldSelfAssignment" value="true" />
<entry key="FindFinalizeInvocations" value="true" />
<entry key="FindFloatEquality" value="true" />
<entry key="FindFloatMath" value="false" />
<entry key="FindHEmismatch" value="true" />
<entry key="FindInconsistentSync2" value="true" />
<entry key="FindJSR166LockMonitorenter" value="true" />
<entry key="FindLocalSelfAssignment2" value="true" />
<entry key="FindMaskedFields" value="true" />
<entry key="FindMismatchedWaitOrNotify" value="true" />
<entry key="FindNakedNotify" value="true" />
<entry key="FindNonSerializableStoreIntoSession" value="false" />
<entry key="FindNonSerializableValuePassedToWriteObject" value="false" />
<entry key="FindNonShortCircuit" value="true" />
<entry key="FindNullDeref" value="true" />
<entry key="FindNullDerefsInvolvingNonShortCircuitEvaluation" value="true" />
<entry key="FindOpenStream" value="true" />
<entry key="FindPuzzlers" value="true" />
<entry key="FindRefComparison" value="true" />
<entry key="FindReturnRef" value="true" />
<entry key="FindRunInvocations" value="true" />
<entry key="FindSelfComparison" value="true" />
<entry key="FindSelfComparison2" value="true" />
<entry key="FindSleepWithLockHeld" value="true" />
<entry key="FindSpinLoop" value="true" />
<entry key="FindSqlInjection" value="true" />
<entry key="FindTwoLockWait" value="true" />
<entry key="FindUncalledPrivateMethods" value="true" />
<entry key="FindUnconditionalWait" value="true" />
<entry key="FindUninitializedGet" value="true" />
<entry key="FindUnrelatedTypesInGenericContainer" value="true" />
<entry key="FindUnreleasedLock" value="true" />
<entry key="FindUnsatisfiedObligation" value="true" />
<entry key="FindUnsyncGet" value="true" />
<entry key="FindUseOfNonSerializableValue" value="true" />
<entry key="FindUselessControlFlow" value="true" />
<entry key="FormatStringChecker" value="true" />
<entry key="FunctionsThatMightBeMistakenForProcedures" value="true" />
<entry key="HugeSharedStringConstants" value="true" />
<entry key="IDivResultCastToDouble" value="true" />
<entry key="IncompatMask" value="true" />
<entry key="InconsistentAnnotations" value="true" />
<entry key="InefficientMemberAccess" value="false" />
<entry key="InefficientToArray" value="true" />
<entry key="InfiniteLoop" value="true" />
<entry key="InfiniteRecursiveLoop" value="true" />
<entry key="InheritanceUnsafeGetResource" value="true" />
<entry key="InitializationChain" value="true" />
<entry key="InitializeNonnullFieldsInConstructor" value="true" />
<entry key="InstantiateStaticClass" value="true" />
<entry key="IntCast2LongAsInstant" value="true" />
<entry key="InvalidJUnitTest" value="true" />
<entry key="IteratorIdioms" value="true" />
<entry key="LazyInit" value="true" />
<entry key="LoadOfKnownNullValue" value="true" />
<entry key="LostLoggerDueToWeakReference" value="true" />
<entry key="MethodReturnCheck" value="true" />
<entry key="Methods" value="true" />
<entry key="MultithreadedInstanceAccess" value="true" />
<entry key="MutableLock" value="true" />
<entry key="MutableStaticFields" value="true" />
<entry key="Naming" value="true" />
<entry key="Noise" value="false" />
<entry key="NoiseNullDeref" value="false" />
<entry key="NoteAnnotationRetention" value="true" />
<entry key="NoteCheckReturnValueAnnotations" value="true" />
<entry key="NoteDirectlyRelevantTypeQualifiers" value="true" />
<entry key="NoteJCIPAnnotation" value="true" />
<entry key="NoteNonNullAnnotations" value="true" />
<entry key="NoteNonnullReturnValues" value="true" />
<entry key="NoteSuppressedWarnings" value="true" />
<entry key="NoteUnconditionalParamDerefs" value="true" />
<entry key="NumberConstructor" value="true" />
<entry key="OverridingEqualsNotSymmetrical" value="true" />
<entry key="PreferZeroLengthArrays" value="true" />
<entry key="PublicSemaphores" value="false" />
<entry key="QuestionableBooleanAssignment" value="true" />
<entry key="ReadOfInstanceFieldInMethodInvokedByConstructorInSuperclass" value="true" />
<entry key="ReadReturnShouldBeChecked" value="true" />
<entry key="RedundantInterfaces" value="true" />
<entry key="ReflectiveClasses" value="true" />
<entry key="RepeatedConditionals" value="true" />
<entry key="ResolveAllReferences" value="false" />
<entry key="RuntimeExceptionCapture" value="true" />
<entry key="SerializableIdiom" value="true" />
<entry key="StartInConstructor" value="true" />
<entry key="StaticCalendarDetector" value="true" />
<entry key="StringConcatenation" value="true" />
<entry key="SuperfluousInstanceOf" value="true" />
<entry key="SuspiciousThreadInterrupted" value="true" />
<entry key="SwitchFallthrough" value="true" />
<entry key="SynchronizationOnSharedBuiltinConstant" value="true" />
<entry key="SynchronizeAndNullCheckField" value="true" />
<entry key="SynchronizeOnClassLiteralNotGetClass" value="true" />
<entry key="SynchronizingOnContentsOfFieldToProtectField" value="true" />
<entry key="TestASM" value="false" />
<entry key="TestDataflowAnalysis" value="false" />
<entry key="TestingGround" value="false" />
<entry key="TestingGround2" value="false" />
<entry key="TrainFieldStoreTypes" value="true" />
<entry key="TrainLongInstantfParams" value="true" />
<entry key="TrainNonNullAnnotations" value="true" />
<entry key="TrainUnconditionalDerefParams" value="true" />
<entry key="URLProblems" value="true" />
<entry key="UncallableMethodOfAnonymousClass" value="true" />
<entry key="UnnecessaryMath" value="true" />
<entry key="UnreadFields" value="true" />
<entry key="UselessSubclassMethod" value="false" />
<entry key="VarArgsProblems" value="true" />
<entry key="VolatileUsage" value="true" />
<entry key="WaitInLoop" value="true" />
<entry key="WrongMapIterator" value="true" />
<entry key="XMLFactoryBypass" value="true" />
</map>
</option>
<option name="_reportCategories">
<map>
<entry key="BAD_PRACTICE" value="true" />
<entry key="CORRECTNESS" value="true" />
<entry key="EXPERIMENTAL" value="true" />
<entry key="I18N" value="true" />
<entry key="MALICIOUS_CODE" value="true" />
<entry key="MT_CORRECTNESS" value="true" />
<entry key="PERFORMANCE" value="true" />
<entry key="SECURITY" value="true" />
<entry key="STYLE" value="true" />
</map>
</option>
<option name="_annotationTypeSettings">
<map>
<entry key="ExpPriority" value="-4473925;-12828863;-8355712;WAVE_UNDERSCORE;0;" />
<entry key="HighPriority" value="-39836;-12828863;-39836;WAVE_UNDERSCORE;1;" />
<entry key="IgnorePriority" value="-4473925;-12828863;-11978414;WAVE_UNDERSCORE;0;" />
<entry key="LowPriority" value="-4473925;-12828863;-10316203;BOXED;0;" />
<entry key="NormalPriority" value="-4473925;-12828863;-10461184;WAVE_UNDERSCORE;2;" />
</map>
</option>
</component>
</project>

View file

@ -1,60 +0,0 @@
<component name="InspectionProjectProfileManager">
<profile version="1.0">
<option name="myName" value="Project Default" />
<option name="myLocal" value="true" />
<inspection_tool class="AssertEqualsCalledOnArray" enabled="true" level="WARNING" enabled_by_default="true" />
<inspection_tool class="AssertEqualsMayBeAssertSame" enabled="true" level="WARNING" enabled_by_default="true" />
<inspection_tool class="AssertsWithoutMessages" enabled="true" level="WARNING" enabled_by_default="true" />
<inspection_tool class="BeforeClassOrAfterClassIsPublicStaticVoidNoArg" enabled="true" level="WARNING" enabled_by_default="true" />
<inspection_tool class="BeforeOrAfterIsPublicVoidNoArg" enabled="true" level="WARNING" enabled_by_default="true" />
<inspection_tool class="ConstantJUnitAssertArgument" enabled="true" level="WARNING" enabled_by_default="true" />
<inspection_tool class="ExpectedExceptionNeverThrown" enabled="true" level="WARNING" enabled_by_default="true" />
<inspection_tool class="FindBugsIDEA" enabled="true" level="SERVER PROBLEM" enabled_by_default="true" />
<inspection_tool class="IgnoredJUnitTest" enabled="true" level="WARNING" enabled_by_default="true" />
<inspection_tool class="JUnit3MethodNamingConvention" enabled="true" level="WARNING" enabled_by_default="true" />
<inspection_tool class="JUnit3StyleTestMethodInJUnit4Class" enabled="true" level="WARNING" enabled_by_default="true" />
<inspection_tool class="JUnit4MethodNamingConvention" enabled="true" level="WARNING" enabled_by_default="true" />
<inspection_tool class="JUnitAbstractTestClassNamingConvention" enabled="true" level="WARNING" enabled_by_default="true">
<option name="m_regex" value="[A-Z][A-Za-z\d]*TestCase" />
<option name="m_minLength" value="12" />
<option name="m_maxLength" value="64" />
</inspection_tool>
<inspection_tool class="JUnitDatapoint" enabled="true" level="WARNING" enabled_by_default="true" />
<inspection_tool class="JUnitRule" enabled="true" level="WARNING" enabled_by_default="true" />
<inspection_tool class="JUnitTestClassNamingConvention" enabled="true" level="WARNING" enabled_by_default="true">
<option name="m_regex" value="[A-Z][A-Za-z\d]*(Test|IT)" />
<option name="m_minLength" value="8" />
<option name="m_maxLength" value="64" />
</inspection_tool>
<inspection_tool class="LoggerInitializedWithForeignClass" enabled="false" level="WARNING" enabled_by_default="false">
<option name="loggerClassName" value="org.apache.log4j.Logger,org.slf4j.LoggerFactory,org.apache.commons.logging.LogFactory,java.util.logging.Logger" />
<option name="loggerFactoryMethodName" value="getLogger,getLogger,getLog,getLogger" />
</inspection_tool>
<inspection_tool class="MigrateAssertToMatcherAssert" enabled="true" level="WARNING" enabled_by_default="true" />
<inspection_tool class="MisorderedAssertEqualsParameters" enabled="true" level="WARNING" enabled_by_default="true" />
<inspection_tool class="MisspelledSetUp" enabled="true" level="WARNING" enabled_by_default="true" />
<inspection_tool class="MisspelledTearDown" enabled="true" level="WARNING" enabled_by_default="true" />
<inspection_tool class="MultipleExceptionsDeclaredOnTestMethod" enabled="true" level="WARNING" enabled_by_default="true" />
<inspection_tool class="ParameterizedParametersStaticCollection" enabled="true" level="WARNING" enabled_by_default="true" />
<inspection_tool class="SetupCallsSuperSetup" enabled="true" level="WARNING" enabled_by_default="true" />
<inspection_tool class="SetupIsPublicVoidNoArg" enabled="true" level="WARNING" enabled_by_default="true" />
<inspection_tool class="SimplifiableJUnitAssertion" enabled="true" level="WARNING" enabled_by_default="true" />
<inspection_tool class="StaticSuite" enabled="true" level="WARNING" enabled_by_default="true" />
<inspection_tool class="SuperTearDownInFinally" enabled="true" level="WARNING" enabled_by_default="true" />
<inspection_tool class="TeardownCallsSuperTeardown" enabled="true" level="WARNING" enabled_by_default="true" />
<inspection_tool class="TeardownIsPublicVoidNoArg" enabled="true" level="WARNING" enabled_by_default="true" />
<inspection_tool class="TestCaseInProductCode" enabled="true" level="WARNING" enabled_by_default="true" />
<inspection_tool class="TestCaseWithConstructor" enabled="true" level="WARNING" enabled_by_default="true" />
<inspection_tool class="TestCaseWithNoTestMethods" enabled="true" level="WARNING" enabled_by_default="true">
<option name="ignoreSupers" value="false" />
</inspection_tool>
<inspection_tool class="TestMethodInProductCode" enabled="true" level="WARNING" enabled_by_default="true" />
<inspection_tool class="TestMethodIsPublicVoidNoArg" enabled="true" level="WARNING" enabled_by_default="true" />
<inspection_tool class="TestMethodWithoutAssertion" enabled="true" level="WARNING" enabled_by_default="true">
<option name="assertionMethods" value="org.junit.Assert,assert.*|fail.*,junit.framework.Assert,assert.*|fail.*,org.mockito.Mockito,verify.*,org.mockito.InOrder,verify,org.junit.rules.ExpectedException,expect.*,org.hamcrest.MatcherAssert,assertThat,org.assertj.core.api.Assertions,assertThat,org.assertj.core.api.SoftAssertions,assertAll" />
<option name="assertKeywordIsAssertion" value="false" />
</inspection_tool>
<inspection_tool class="UnconstructableTestCase" enabled="true" level="WARNING" enabled_by_default="true" />
<inspection_tool class="UseOfObsoleteAssert" enabled="true" level="WARNING" enabled_by_default="true" />
</profile>
</component>

View file

@ -1,7 +0,0 @@
<component name="InspectionProjectProfileManager">
<settings>
<option name="PROJECT_PROFILE" value="Project Default" />
<option name="USE_PROJECT_PROFILE" value="true" />
<version value="1.0" />
</settings>
</component>

View file

@ -1,42 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="EntryPointsManager">
<entry_points version="2.0" />
</component>
<component name="MavenProjectsManager">
<option name="originalFiles">
<list>
<option value="$PROJECT_DIR$/pom.xml" />
</list>
</option>
</component>
<component name="ProjectLevelVcsManager" settingsEditedManually="false">
<OptionsSetting value="true" id="Add" />
<OptionsSetting value="true" id="Remove" />
<OptionsSetting value="true" id="Checkout" />
<OptionsSetting value="true" id="Update" />
<OptionsSetting value="true" id="Status" />
<OptionsSetting value="true" id="Edit" />
<ConfirmationsSetting value="0" id="Add" />
<ConfirmationsSetting value="0" id="Remove" />
</component>
<component name="ProjectRootManager" version="2" languageLevel="JDK_1_6" assert-keyword="true" jdk-15="true" project-jdk-name="1.8" project-jdk-type="JavaSDK">
<output url="file://$PROJECT_DIR$/classes" />
</component>
<component name="masterDetails">
<states>
<state key="ProjectJDKs.UI">
<settings>
<last-edited>1.8</last-edited>
<splitter-proportions>
<option name="proportions">
<list>
<option value="0.2" />
</list>
</option>
</splitter-proportions>
</settings>
</state>
</states>
</component>
</project>

View file

@ -1,8 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="ProjectModuleManager">
<modules>
<module fileurl="file://$PROJECT_DIR$/node.iml" filepath="$PROJECT_DIR$/node.iml" />
</modules>
</component>
</project>

View file

@ -1,6 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="VcsDirectoryMappings">
<mapping directory="" vcs="Git" />
</component>
</project>

View file

@ -1,3 +1,5 @@
language: java
jdk:
- oraclejdk8
after_success:
- mvn clean test jacoco:report coveralls:report

View file

@ -1,6 +1,13 @@
CHANGELOG
=========
0.4.0
------
* Upgrade kemitix-parent to 2.0.0
* Add ImmutableTree implementation
* Switch to static factory constructors
0.3.0
------

View file

@ -1,3 +1,6 @@
[![Build Status](https://travis-ci.org/kemitix/node.svg?branch=develop)](https://travis-ci.org/kemitix/node)
[![Coverage Status](https://coveralls.io/repos/github/kemitix/node/badge.svg?branch=develop)](https://coveralls.io/github/kemitix/node?branch=develop)
# node
[![Join the chat at https://gitter.im/kemitix/node](https://badges.gitter.im/kemitix/node.svg)](https://gitter.im/kemitix/node?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge)

View file

@ -1,192 +0,0 @@
<?xml version="1.0"?>
<!DOCTYPE module PUBLIC
"-//Puppy Crawl//DTD Check Configuration 1.3//EN"
"http://www.puppycrawl.com/dtds/configuration_1_3.dtd">
<!--
Checkstyle configuration that checks the sun coding conventions from:
- the Java Language Specification at
http://java.sun.com/docs/books/jls/second_edition/html/index.html
- the Sun Code Conventions at http://java.sun.com/docs/codeconv/
- the Javadoc guidelines at
http://java.sun.com/j2se/javadoc/writingdoccomments/index.html
- the JDK Api documentation http://java.sun.com/j2se/docs/api/index.html
- some best practices
Checkstyle is very configurable. Be sure to read the documentation at
http://checkstyle.sf.net (or in your downloaded distribution).
Most Checks are configurable, be sure to consult the documentation.
To completely disable a check, just comment it out or delete it from the file.
Finally, it is worth reading the documentation.
-->
<module name="Checker">
<!--
If you set the basedir property below, then all reported file
names will be relative to the specified directory. See
http://checkstyle.sourceforge.net/5.x/config.html#Checker
<property name="basedir" value="${basedir}"/>
-->
<!-- Checks that a package-info.java file exists for each package. -->
<!-- See http://checkstyle.sf.net/config_javadoc.html#JavadocPackage -->
<module name="JavadocPackage"/>
<!-- Checks whether files end with a new line. -->
<!-- See http://checkstyle.sf.net/config_misc.html#NewlineAtEndOfFile -->
<module name="NewlineAtEndOfFile"/>
<!-- Checks that property files contain the same keys. -->
<!-- See http://checkstyle.sf.net/config_misc.html#Translation -->
<module name="Translation"/>
<!-- Checks for Size Violations. -->
<!-- See http://checkstyle.sf.net/config_sizes.html -->
<module name="FileLength"/>
<!-- Checks for whitespace -->
<!-- See http://checkstyle.sf.net/config_whitespace.html -->
<module name="FileTabCharacter"/>
<!-- Miscellaneous other checks. -->
<!-- See http://checkstyle.sf.net/config_misc.html -->
<module name="RegexpSingleline">
<property name="format" value="\s+$"/>
<property name="minimum" value="0"/>
<property name="maximum" value="0"/>
<property name="message" value="Line has trailing spaces."/>
</module>
<!-- Checks for Headers -->
<!-- See http://checkstyle.sf.net/config_header.html -->
<!-- <module name="Header"> -->
<!-- <property name="headerFile" value="${checkstyle.header.file}"/> -->
<!-- <property name="fileExtensions" value="java"/> -->
<!-- </module> -->
<module name="TreeWalker">
<!-- Support @SuppressWarnings annotation -->
<!-- See http://checkstyle.sourceforge.net/config.html -->
<module name="SuppressWarningsHolder"/>
<module name="FileContentsHolder"/>
<!-- Checks for Javadoc comments. -->
<!-- See http://checkstyle.sf.net/config_javadoc.html -->
<module name="JavadocMethod">
<property name="scope" value="public"/>
</module>
<module name="JavadocType"/>
<!--<module name="JavadocVariable"/>-->
<module name="JavadocStyle"/>
<!-- Checks for Naming Conventions. -->
<!-- See http://checkstyle.sf.net/config_naming.html -->
<module name="ConstantName"/>
<module name="LocalFinalVariableName"/>
<module name="LocalVariableName"/>
<module name="MemberName"/>
<module name="MethodName"/>
<module name="PackageName"/>
<module name="ParameterName"/>
<module name="StaticVariableName"/>
<module name="TypeName"/>
<!-- Checks for imports -->
<!-- See http://checkstyle.sf.net/config_import.html -->
<module name="AvoidStarImport"/>
<module name="IllegalImport"/> <!-- defaults to sun.* packages -->
<module name="RedundantImport"/>
<module name="UnusedImports"/>
<!-- Checks for Size Violations. -->
<!-- See http://checkstyle.sf.net/config_sizes.html -->
<module name="LineLength"/>
<module name="MethodLength"/>
<module name="ParameterNumber"/>
<!-- Checks for whitespace -->
<!-- See http://checkstyle.sf.net/config_whitespace.html -->
<module name="EmptyForIteratorPad"/>
<module name="GenericWhitespace"/>
<module name="MethodParamPad"/>
<module name="NoWhitespaceAfter"/>
<module name="NoWhitespaceBefore"/>
<module name="OperatorWrap"/>
<module name="ParenPad"/>
<module name="TypecastParenPad"/>
<module name="WhitespaceAfter"/>
<module name="WhitespaceAround"/>
<!-- Modifier Checks -->
<!-- See http://checkstyle.sf.net/config_modifiers.html -->
<module name="ModifierOrder"/>
<module name="RedundantModifier"/>
<!-- Checks for blocks. You know, those {}'s -->
<!-- See http://checkstyle.sf.net/config_blocks.html -->
<module name="AvoidNestedBlocks"/>
<module name="EmptyBlock"/>
<module name="LeftCurly"/>
<module name="NeedBraces"/>
<module name="RightCurly"/>
<!-- Checks for common coding problems -->
<!-- See http://checkstyle.sf.net/config_coding.html -->
<module name="AvoidInlineConditionals"/>
<module name="EmptyStatement"/>
<module name="EqualsHashCode"/>
<module name="HiddenField">
<property name="ignoreConstructorParameter" value="true"/>
<property name="ignoreSetter" value="true"/>
<property name="setterCanReturnItsClass" value="true"/>
</module>
<module name="IllegalInstantiation"/>
<module name="InnerAssignment"/>
<module name="MagicNumber"/>
<module name="MissingSwitchDefault"/>
<module name="SimplifyBooleanExpression"/>
<module name="SimplifyBooleanReturn"/>
<!-- Checks for class design -->
<!-- See http://checkstyle.sf.net/config_design.html -->
<!--<module name="DesignForExtension"/>-->
<module name="FinalClass"/>
<module name="HideUtilityClassConstructor"/>
<module name="InterfaceIsType"/>
<module name="VisibilityModifier"/>
<!-- Miscellaneous other checks. -->
<!-- See http://checkstyle.sf.net/config_misc.html -->
<module name="ArrayTypeStyle"/>
<module name="FinalParameters"/>
<module name="TodoComment"/>
<module name="UpperEll"/>
</module>
<module name="SuppressWarningsFilter"/>
<module name="SuppressionCommentFilter"/>
</module>

180
node.iml
View file

@ -1,180 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<module org.jetbrains.idea.maven.project.MavenProjectsManager.isMavenModule="true" type="JAVA_MODULE" version="4">
<component name="NewModuleRootManager" LANGUAGE_LEVEL="JDK_1_8" inherit-compiler-output="false">
<output url="file://$MODULE_DIR$/target/classes" />
<output-test url="file://$MODULE_DIR$/target/test-classes" />
<content url="file://$MODULE_DIR$">
<sourceFolder url="file://$MODULE_DIR$/src/main/java" isTestSource="false" />
<sourceFolder url="file://$MODULE_DIR$/src/main/resources" type="java-resource" />
<sourceFolder url="file://$MODULE_DIR$/src/test/java" isTestSource="true" />
<excludeFolder url="file://$MODULE_DIR$/target" />
</content>
<orderEntry type="inheritedJdk" />
<orderEntry type="sourceFolder" forTests="false" />
<orderEntry type="library" scope="TEST" name="Maven: org.projectlombok:lombok:1.16.8" level="project" />
<orderEntry type="library" scope="TEST" name="Maven: junit:junit:4.12" level="project" />
<orderEntry type="library" scope="TEST" name="Maven: org.hamcrest:hamcrest-core:1.3" level="project" />
<orderEntry type="library" scope="TEST" name="Maven: org.assertj:assertj-core:3.4.1" level="project" />
</component>
<component name="org.twodividedbyzero.idea.findbugs">
<option name="_detectors">
<map>
<entry key="AppendingToAnObjectOutputStream" value="true" />
<entry key="AtomicityProblem" value="true" />
<entry key="BadAppletConstructor" value="false" />
<entry key="BadResultSetAccess" value="true" />
<entry key="BadSyntaxForRegularExpression" value="true" />
<entry key="BadUseOfReturnValue" value="true" />
<entry key="BadlyOverriddenAdapter" value="true" />
<entry key="BooleanReturnNull" value="true" />
<entry key="BuildInterproceduralCallGraph" value="false" />
<entry key="BuildObligationPolicyDatabase" value="true" />
<entry key="CallToUnsupportedMethod" value="false" />
<entry key="CalledMethods" value="true" />
<entry key="CheckCalls" value="false" />
<entry key="CheckExpectedWarnings" value="false" />
<entry key="CheckImmutableAnnotation" value="true" />
<entry key="CheckTypeQualifiers" value="true" />
<entry key="CloneIdiom" value="true" />
<entry key="ComparatorIdiom" value="true" />
<entry key="ConfusedInheritance" value="true" />
<entry key="ConfusionBetweenInheritedAndOuterMethod" value="true" />
<entry key="CrossSiteScripting" value="true" />
<entry key="DefaultEncodingDetector" value="true" />
<entry key="DoInsideDoPrivileged" value="true" />
<entry key="DontCatchIllegalMonitorStateException" value="true" />
<entry key="DontIgnoreResultOfPutIfAbsent" value="true" />
<entry key="DontUseEnum" value="true" />
<entry key="DroppedException" value="true" />
<entry key="DumbMethodInvocations" value="true" />
<entry key="DumbMethods" value="true" />
<entry key="DuplicateBranches" value="true" />
<entry key="EmptyZipFileEntry" value="true" />
<entry key="EqualsOperandShouldHaveClassCompatibleWithThis" value="true" />
<entry key="ExplicitSerialization" value="true" />
<entry key="FieldItemSummary" value="true" />
<entry key="FinalizerNullsFields" value="true" />
<entry key="FindBadCast2" value="true" />
<entry key="FindBadForLoop" value="true" />
<entry key="FindBugsSummaryStats" value="true" />
<entry key="FindCircularDependencies" value="false" />
<entry key="FindDeadLocalStores" value="true" />
<entry key="FindDoubleCheck" value="true" />
<entry key="FindEmptySynchronizedBlock" value="true" />
<entry key="FindFieldSelfAssignment" value="true" />
<entry key="FindFinalizeInvocations" value="true" />
<entry key="FindFloatEquality" value="true" />
<entry key="FindFloatMath" value="false" />
<entry key="FindHEmismatch" value="true" />
<entry key="FindInconsistentSync2" value="true" />
<entry key="FindJSR166LockMonitorenter" value="true" />
<entry key="FindLocalSelfAssignment2" value="true" />
<entry key="FindMaskedFields" value="true" />
<entry key="FindMismatchedWaitOrNotify" value="true" />
<entry key="FindNakedNotify" value="true" />
<entry key="FindNonSerializableStoreIntoSession" value="false" />
<entry key="FindNonSerializableValuePassedToWriteObject" value="false" />
<entry key="FindNonShortCircuit" value="true" />
<entry key="FindNullDeref" value="true" />
<entry key="FindNullDerefsInvolvingNonShortCircuitEvaluation" value="true" />
<entry key="FindOpenStream" value="true" />
<entry key="FindPuzzlers" value="true" />
<entry key="FindRefComparison" value="true" />
<entry key="FindReturnRef" value="true" />
<entry key="FindRunInvocations" value="true" />
<entry key="FindSelfComparison" value="true" />
<entry key="FindSelfComparison2" value="true" />
<entry key="FindSleepWithLockHeld" value="true" />
<entry key="FindSpinLoop" value="true" />
<entry key="FindSqlInjection" value="true" />
<entry key="FindTwoLockWait" value="true" />
<entry key="FindUncalledPrivateMethods" value="true" />
<entry key="FindUnconditionalWait" value="true" />
<entry key="FindUninitializedGet" value="true" />
<entry key="FindUnrelatedTypesInGenericContainer" value="true" />
<entry key="FindUnreleasedLock" value="true" />
<entry key="FindUnsatisfiedObligation" value="true" />
<entry key="FindUnsyncGet" value="true" />
<entry key="FindUseOfNonSerializableValue" value="true" />
<entry key="FindUselessControlFlow" value="true" />
<entry key="FormatStringChecker" value="true" />
<entry key="FunctionsThatMightBeMistakenForProcedures" value="true" />
<entry key="HugeSharedStringConstants" value="true" />
<entry key="IDivResultCastToDouble" value="true" />
<entry key="IncompatMask" value="true" />
<entry key="InconsistentAnnotations" value="true" />
<entry key="InefficientMemberAccess" value="false" />
<entry key="InefficientToArray" value="true" />
<entry key="InfiniteLoop" value="true" />
<entry key="InfiniteRecursiveLoop" value="true" />
<entry key="InheritanceUnsafeGetResource" value="true" />
<entry key="InitializationChain" value="true" />
<entry key="InitializeNonnullFieldsInConstructor" value="true" />
<entry key="InstantiateStaticClass" value="true" />
<entry key="IntCast2LongAsInstant" value="true" />
<entry key="InvalidJUnitTest" value="true" />
<entry key="IteratorIdioms" value="true" />
<entry key="LazyInit" value="true" />
<entry key="LoadOfKnownNullValue" value="true" />
<entry key="LostLoggerDueToWeakReference" value="true" />
<entry key="MethodReturnCheck" value="true" />
<entry key="Methods" value="true" />
<entry key="MultithreadedInstanceAccess" value="true" />
<entry key="MutableLock" value="true" />
<entry key="MutableStaticFields" value="true" />
<entry key="Naming" value="true" />
<entry key="Noise" value="false" />
<entry key="NoiseNullDeref" value="false" />
<entry key="NoteAnnotationRetention" value="true" />
<entry key="NoteCheckReturnValueAnnotations" value="true" />
<entry key="NoteDirectlyRelevantTypeQualifiers" value="true" />
<entry key="NoteJCIPAnnotation" value="true" />
<entry key="NoteNonNullAnnotations" value="true" />
<entry key="NoteNonnullReturnValues" value="true" />
<entry key="NoteSuppressedWarnings" value="true" />
<entry key="NoteUnconditionalParamDerefs" value="true" />
<entry key="NumberConstructor" value="true" />
<entry key="OverridingEqualsNotSymmetrical" value="true" />
<entry key="PreferZeroLengthArrays" value="true" />
<entry key="PublicSemaphores" value="false" />
<entry key="QuestionableBooleanAssignment" value="true" />
<entry key="ReadOfInstanceFieldInMethodInvokedByConstructorInSuperclass" value="true" />
<entry key="ReadReturnShouldBeChecked" value="true" />
<entry key="RedundantInterfaces" value="true" />
<entry key="ReflectiveClasses" value="true" />
<entry key="RepeatedConditionals" value="true" />
<entry key="ResolveAllReferences" value="false" />
<entry key="RuntimeExceptionCapture" value="true" />
<entry key="SerializableIdiom" value="true" />
<entry key="StartInConstructor" value="true" />
<entry key="StaticCalendarDetector" value="true" />
<entry key="StringConcatenation" value="true" />
<entry key="SuperfluousInstanceOf" value="true" />
<entry key="SuspiciousThreadInterrupted" value="true" />
<entry key="SwitchFallthrough" value="true" />
<entry key="SynchronizationOnSharedBuiltinConstant" value="true" />
<entry key="SynchronizeAndNullCheckField" value="true" />
<entry key="SynchronizeOnClassLiteralNotGetClass" value="true" />
<entry key="SynchronizingOnContentsOfFieldToProtectField" value="true" />
<entry key="TestASM" value="false" />
<entry key="TestDataflowAnalysis" value="false" />
<entry key="TestingGround" value="false" />
<entry key="TestingGround2" value="false" />
<entry key="TrainFieldStoreTypes" value="true" />
<entry key="TrainLongInstantfParams" value="true" />
<entry key="TrainNonNullAnnotations" value="true" />
<entry key="TrainUnconditionalDerefParams" value="true" />
<entry key="URLProblems" value="true" />
<entry key="UncallableMethodOfAnonymousClass" value="true" />
<entry key="UnnecessaryMath" value="true" />
<entry key="UnreadFields" value="true" />
<entry key="UselessSubclassMethod" value="false" />
<entry key="VarArgsProblems" value="true" />
<entry key="VolatileUsage" value="true" />
<entry key="WaitInLoop" value="true" />
<entry key="WrongMapIterator" value="true" />
<entry key="XMLFactoryBypass" value="true" />
</map>
</option>
</component>
</module>

23
pom.xml
View file

@ -2,7 +2,7 @@
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<artifactId>node</artifactId>
<version>0.3.0</version>
<version>0.4.0</version>
<packaging>jar</packaging>
<name>Node</name>
@ -11,11 +11,13 @@
<parent>
<groupId>net.kemitix</groupId>
<artifactId>kemitix-parent</artifactId>
<version>0.6.0</version>
<version>2.0.0</version>
</parent>
<properties>
<assertj.version>3.4.1</assertj.version>
<coveralls-maven-plugin.version>4.2.0</coveralls-maven-plugin.version>
<trajano-commons-testing.version>2.1.0</trajano-commons-testing.version>
</properties>
<issueManagement>
@ -33,12 +35,21 @@
<inceptionYear>2016</inceptionYear>
<build>
<plugins>
<plugin>
<groupId>org.eluder.coveralls</groupId>
<artifactId>coveralls-maven-plugin</artifactId>
<version>${coveralls-maven-plugin.version}</version>
</plugin>
</plugins>
</build>
<dependencies>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>1.16.8</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>junit</groupId>
@ -58,5 +69,11 @@
<version>${assertj.version}</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>net.trajano.commons</groupId>
<artifactId>commons-testing</artifactId>
<version>${trajano-commons-testing.version}</version>
<scope>test</scope>
</dependency>
</dependencies>
</project>

View file

@ -0,0 +1,155 @@
package net.kemitix.node;
import lombok.NonNull;
import java.util.HashSet;
import java.util.List;
import java.util.Optional;
import java.util.Set;
/**
* An abstract node item, providing default implementations for most read-only
* operations.
*
* @author Paul Campbell
*
* @param <T> the type of data stored in each node
*/
abstract class AbstractNodeItem<T> implements Node<T> {
private T data;
private String name;
private Node<T> parent;
private final Set<Node<T>> children;
protected AbstractNodeItem(
final T data, final String name, final Node<T> parent,
final Set<Node<T>> children) {
this.data = data;
this.name = name;
this.parent = parent;
this.children = children;
}
@Override
public String getName() {
return name;
}
@Override
public Optional<T> getData() {
return Optional.ofNullable(data);
}
@Override
public boolean isEmpty() {
return data == null;
}
@Override
public Optional<Node<T>> getParent() {
return Optional.ofNullable(parent);
}
@Override
public Set<Node<T>> getChildren() {
return new HashSet<>(children);
}
/**
* Fetches the node for the child if present.
*
* @param child the child's data to search for
*
* @return an {@link Optional} containing the child node if found
*/
@Override
public Optional<Node<T>> findChild(@NonNull final T child) {
return children.stream().filter(node -> {
final Optional<T> d = node.getData();
return d.isPresent() && d.get().equals(child);
}).findAny();
}
@Override
public Node<T> getChild(final T child) {
return findChild(child).orElseThrow(
() -> new NodeException("Child not found"));
}
/**
* Checks if the node is an ancestor.
*
* @param node the potential ancestor
*
* @return true if the node is an ancestor
*/
@Override
public boolean isDescendantOf(final Node<T> node) {
return parent != null && (node.equals(parent) || parent.isDescendantOf(
node));
}
/**
* Walks the node tree using the path to select each child.
*
* @param path the path to the desired child
*
* @return the child or null
*/
@Override
public Optional<Node<T>> findInPath(@NonNull final List<T> path) {
if (path.isEmpty()) {
return Optional.empty();
}
Node<T> current = this;
for (T item : path) {
final Optional<Node<T>> child = current.findChild(item);
if (child.isPresent()) {
current = child.get();
} else {
current = null;
break;
}
}
return Optional.ofNullable(current);
}
@Override
public Optional<Node<T>> findChildByName(@NonNull final String named) {
return children.stream()
.filter(n -> n.getName().equals(named))
.findAny();
}
@Override
public Node<T> getChildByName(final String named) {
return findChildByName(named).orElseThrow(
() -> new NodeException("Named child not found"));
}
@Override
public String drawTree(final int depth) {
final StringBuilder sb = new StringBuilder();
final String unnamed = "(unnamed)";
if (isNamed()) {
sb.append(formatByDepth(name, depth));
} else if (!children.isEmpty()) {
sb.append(formatByDepth(unnamed, depth));
}
getChildren().forEach(c -> sb.append(c.drawTree(depth + 1)));
return sb.toString();
}
private String formatByDepth(final String value, final int depth) {
return String.format("[%1$" + (depth + value.length()) + "s]\n", value);
}
@Override
public boolean isNamed() {
return name != null && name.length() > 0;
}
}

View file

@ -0,0 +1,95 @@
package net.kemitix.node;
import java.util.List;
import java.util.Set;
/**
* Represents an immutable tree of nodes.
*
* <p>Due to the use of generics the data within a node may not be immutable.
* (We can't create a defensive copy.) So if a user were to use {@code
* getData()} they could then modify the original data within the node. This
* wouldn't affect the integrity of the node tree structure, however.</p>
*
* @author Paul Campbell
*
* @param <T> the type of data stored in each node
*/
final class ImmutableNodeItem<T> extends AbstractNodeItem<T> {
private static final String IMMUTABLE_OBJECT = "Immutable object";
private ImmutableNodeItem(
final T data, final String name, final Node<T> parent,
final Set<Node<T>> children) {
super(data, name, parent, children);
}
static <T> ImmutableNodeItem<T> newRoot(
final T data, final String name, final Set<Node<T>> children) {
return new ImmutableNodeItem<>(data, name, null, children);
}
static <T> ImmutableNodeItem<T> newChild(
final T data, final String name, final Node<T> parent,
final Set<Node<T>> children) {
return new ImmutableNodeItem<>(data, name, parent, children);
}
@Override
public void setName(final String name) {
throw new UnsupportedOperationException(IMMUTABLE_OBJECT);
}
@Override
public void setData(final T data) {
throw new UnsupportedOperationException(IMMUTABLE_OBJECT);
}
@Override
public void setParent(final Node<T> parent) {
throw new UnsupportedOperationException(IMMUTABLE_OBJECT);
}
@Override
public void addChild(final Node<T> child) {
throw new UnsupportedOperationException(IMMUTABLE_OBJECT);
}
@Override
public Node<T> createChild(final T child) {
throw new UnsupportedOperationException(IMMUTABLE_OBJECT);
}
@Override
public Node<T> createChild(final T child, final String name) {
throw new UnsupportedOperationException(IMMUTABLE_OBJECT);
}
@Override
public void createDescendantLine(final List<T> descendants) {
throw new UnsupportedOperationException(IMMUTABLE_OBJECT);
}
@Override
public Node<T> findOrCreateChild(final T child) {
return findChild(child).orElseThrow(
() -> new UnsupportedOperationException(IMMUTABLE_OBJECT));
}
@Override
public void insertInPath(final Node<T> node, final String... path) {
throw new UnsupportedOperationException(IMMUTABLE_OBJECT);
}
@Override
public void removeChild(final Node<T> node) {
throw new UnsupportedOperationException(IMMUTABLE_OBJECT);
}
@Override
public void removeParent() {
throw new UnsupportedOperationException(IMMUTABLE_OBJECT);
}
}

View file

@ -7,9 +7,9 @@ import java.util.Set;
/**
* An interface for tree node items.
*
* @param <T> the type of data held in each node
* @author Paul Campbell
*
* @author pcampbell
* @param <T> the type of data held in each node
*/
public interface Node<T> {
@ -113,6 +113,9 @@ public interface Node<T> {
* @param child the child's data to search or create with
*
* @return the found or created child node
*
* @deprecated use node.findChild(child).orElseGet(() ->
* node.createChild(child));
*/
@Deprecated
Node<T> findOrCreateChild(T child);

View file

@ -1,27 +1,27 @@
package net.kemitix.node;
import lombok.NonNull;
import lombok.val;
import java.util.Arrays;
import java.util.HashSet;
import java.util.List;
import java.util.Optional;
import java.util.Set;
import java.util.function.Function;
/**
* Represents a tree of nodes.
*
* @param <T> the type of data stored in each node
* @author Paul Campbell
*
* @author pcampbell
* @param <T> the type of data stored in each node
*/
public class NodeItem<T> implements Node<T> {
class NodeItem<T> implements Node<T> {
private T data;
private final Set<Node<T>> children = new HashSet<>();
private Function<Node<T>, String> nameSupplier;
private Node<T> parent;
private String name;
@ -32,7 +32,7 @@ public class NodeItem<T> implements Node<T> {
* @param data the data or null
* @param name the name
*/
public NodeItem(final T data, final String name) {
NodeItem(final T data, final String name) {
this(data);
this.name = name;
}
@ -42,21 +42,8 @@ public class NodeItem<T> implements Node<T> {
*
* @param data the data or null
*/
public NodeItem(final T data) {
NodeItem(final T data) {
this.data = data;
this.nameSupplier = (n) -> null;
}
/**
* Creates root node with a name supplier.
*
* @param data the data or null
* @param nameSupplier the name supplier function
*/
public NodeItem(
final T data, final Function<Node<T>, String> nameSupplier) {
this(data);
this.nameSupplier = nameSupplier;
}
/**
@ -65,7 +52,7 @@ public class NodeItem<T> implements Node<T> {
* @param data the data or null
* @param parent the parent node
*/
public NodeItem(final T data, final Node<T> parent) {
NodeItem(final T data, final Node<T> parent) {
this.data = data;
setParent(parent);
}
@ -77,44 +64,14 @@ public class NodeItem<T> implements Node<T> {
* @param name the name
* @param parent the parent node
*/
public NodeItem(final T data, final String name, final Node<T> parent) {
NodeItem(final T data, final String name, final Node<T> parent) {
this.data = data;
this.name = name;
setParent(parent);
}
/**
* Creates a node with a name supplier and a parent.
*
* @param data the data or null
* @param nameSupplier the name supplier function
* @param parent the parent node
*/
public NodeItem(
final T data, final Function<Node<T>, String> nameSupplier,
final Node<T> parent) {
this(data, nameSupplier);
setParent(parent);
}
private String generateName() {
return getNameSupplier().apply(this);
}
private Function<Node<T>, String> getNameSupplier() {
if (nameSupplier != null) {
return nameSupplier;
}
// no test for parent as root nodes will always have a default name
// supplier
return ((NodeItem<T>) parent).getNameSupplier();
}
@Override
public String getName() {
if (name == null) {
return generateName();
}
return name;
}
@ -154,32 +111,35 @@ public class NodeItem<T> implements Node<T> {
* @param child the node to add
*/
@Override
public void addChild(final Node<T> child) {
if (child == null) {
throw new NullPointerException("child");
}
if (this.equals(child) || isDescendantOf(child)) {
throw new NodeException("Child is an ancestor");
}
if (child.isNamed()) {
final Optional<Node<T>> existingChild = findChildByName(
child.getName());
if (existingChild.isPresent() && existingChild.get() != child) {
throw new NodeException(
"Node with that name already exists here");
}
}
public void addChild(@NonNull final Node<T> child) {
verifyChildIsNotAnAncestor(child);
verifyChildWithSameNameDoesNotAlreadyExist(child);
children.add(child);
// update the child's parent if they don't have one or it is not this
Optional<Node<T>> childParent = child.getParent();
boolean isOrphan = !childParent.isPresent();
boolean hasDifferentParent = !isOrphan && !childParent.get()
.equals(this);
if (isOrphan || hasDifferentParent) {
val childParent = child.getParent();
if (!childParent.isPresent() || !childParent.get().equals(this)) {
child.setParent(this);
}
}
private void verifyChildWithSameNameDoesNotAlreadyExist(
final @NonNull Node<T> child) {
if (child.isNamed()) {
findChildByName(child.getName())
.filter(existingChild -> existingChild != child)
.ifPresent(existingChild -> {
throw new NodeException(
"Node with that name already exists here");
});
}
}
private void verifyChildIsNotAnAncestor(final @NonNull Node<T> child) {
if (this.equals(child) || isDescendantOf(child)) {
throw new NodeException("Child is an ancestor");
}
}
/**
* Creates a new node and adds it as a child of the current node.
*
@ -188,10 +148,7 @@ public class NodeItem<T> implements Node<T> {
* @return the new child node
*/
@Override
public Node<T> createChild(final T child) {
if (child == null) {
throw new NullPointerException("child");
}
public Node<T> createChild(@NonNull final T child) {
return new NodeItem<>(child, this);
}
@ -210,10 +167,7 @@ public class NodeItem<T> implements Node<T> {
* @param descendants the line of descendants from the current node
*/
@Override
public void createDescendantLine(final List<T> descendants) {
if (descendants == null) {
throw new NullPointerException("descendants");
}
public void createDescendantLine(@NonNull final List<T> descendants) {
if (!descendants.isEmpty()) {
findOrCreateChild(descendants.get(0)).createDescendantLine(
descendants.subList(1, descendants.size()));
@ -227,12 +181,13 @@ public class NodeItem<T> implements Node<T> {
* @param child the child's data to search or create with
*
* @return the found or created child node
*
* @deprecated use node.findChild(child).orElseGet(() -> node.createChild
* (child));
*/
@Override
public Node<T> findOrCreateChild(final T child) {
if (child == null) {
throw new NullPointerException("child");
}
@Deprecated
public Node<T> findOrCreateChild(@NonNull final T child) {
return findChild(child).orElseGet(() -> createChild(child));
}
@ -244,14 +199,11 @@ public class NodeItem<T> implements Node<T> {
* @return an {@link Optional} containing the child node if found
*/
@Override
public Optional<Node<T>> findChild(final T child) {
if (child == null) {
throw new NullPointerException("child");
}
return children.stream()
.filter(n -> !n.isEmpty())
.filter(n -> n.getData().get().equals(child))
.findAny();
public Optional<Node<T>> findChild(@NonNull final T child) {
return children.stream().filter(node -> {
final Optional<T> d = node.getData();
return d.isPresent() && d.get().equals(child);
}).findAny();
}
@Override
@ -282,10 +234,7 @@ public class NodeItem<T> implements Node<T> {
* @param parent the new parent node
*/
@Override
public final void setParent(final Node<T> parent) {
if (parent == null) {
throw new NullPointerException("parent");
}
public final void setParent(@NonNull final Node<T> parent) {
if (this.equals(parent) || parent.isDescendantOf(this)) {
throw new NodeException("Parent is a descendant");
}
@ -304,64 +253,68 @@ public class NodeItem<T> implements Node<T> {
* @return the child or null
*/
@Override
public Optional<Node<T>> findInPath(final List<T> path) {
if (path == null) {
throw new NullPointerException("path");
public Optional<Node<T>> findInPath(@NonNull final List<T> path) {
if (path.isEmpty()) {
return Optional.empty();
}
if (path.size() > 0) {
Optional<Node<T>> found = findChild(path.get(0));
if (found.isPresent()) {
if (path.size() > 1) {
return found.get().findInPath(path.subList(1, path.size()));
}
return found;
Node<T> current = this;
for (T item : path) {
final Optional<Node<T>> child = current.findChild(item);
if (child.isPresent()) {
current = child.get();
} else {
current = null;
break;
}
}
return Optional.empty();
return Optional.ofNullable(current);
}
@Override
public void insertInPath(final Node<T> nodeItem, final String... path) {
if (path.length == 0) {
if (!nodeItem.isNamed()) { // nothing to conflict with
addChild(nodeItem);
return;
}
String nodeName = nodeItem.getName();
final Optional<Node<T>> childNamed = findChildByName(nodeName);
if (!childNamed.isPresent()) { // nothing with the same name exists
addChild(nodeItem);
return;
}
// we have an existing node with the same name
final Node<T> existing = childNamed.get();
if (!existing.isEmpty()) {
throw new NodeException("A non-empty node named '" + nodeName
+ "' already exists here");
} else {
nodeItem.getData().ifPresent(existing::setData);
}
return;
}
String item = path[0];
final Optional<Node<T>> childNamed = findChildByName(item);
Node<T> child;
if (!childNamed.isPresent()) {
child = new NodeItem<>(null, item, this);
insertChild(nodeItem);
} else {
child = childNamed.get();
val item = path[0];
findChildByName(item)
.orElseGet(() -> new NodeItem<>(null, item, this))
.insertInPath(nodeItem,
Arrays.copyOfRange(path, 1, path.length));
}
}
private void insertChild(final Node<T> nodeItem) {
if (nodeItem.isNamed()) {
insertNamedChild(nodeItem);
} else {
// nothing to conflict with
addChild(nodeItem);
}
}
private void insertNamedChild(final Node<T> nodeItem) {
val childByName = findChildByName(nodeItem.getName());
if (childByName.isPresent()) {
// we have an existing node with the same name
val existing = childByName.get();
if (existing.isEmpty()) {
// place any data in the new node into the existing empty node
nodeItem.getData().ifPresent(existing::setData);
} else {
throw new NodeException("A non-empty node named '"
+ nodeItem.getName() + "' already exists here");
}
} else {
// nothing with the same name exists
addChild(nodeItem);
}
child.insertInPath(nodeItem, Arrays.copyOfRange(path, 1, path.length));
}
@Override
public Optional<Node<T>> findChildByName(final String named) {
if (named == null) {
throw new NullPointerException("name");
}
public Optional<Node<T>> findChildByName(@NonNull final String named) {
return children.stream()
.filter((Node<T> t) -> t.getName().equals(named))
.findAny();
.filter((Node<T> t) -> t.getName().equals(named))
.findAny();
}
@Override
@ -378,17 +331,18 @@ public class NodeItem<T> implements Node<T> {
final StringBuilder sb = new StringBuilder();
final String unnamed = "(unnamed)";
if (isNamed()) {
sb.append(String.format("[%1$" + (depth + name.length()) + "s]\n",
name));
sb.append(formatByDepth(name, depth));
} else if (!children.isEmpty()) {
sb.append(
String.format("[%1$" + (depth + unnamed.length()) + "s]\n",
unnamed));
sb.append(formatByDepth(unnamed, depth));
}
getChildren().stream().forEach(c -> sb.append(c.drawTree(depth + 1)));
getChildren().forEach(c -> sb.append(c.drawTree(depth + 1)));
return sb.toString();
}
private String formatByDepth(final String value, final int depth) {
return String.format("[%1$" + (depth + value.length()) + "s]\n", value);
}
@Override
public boolean isNamed() {
String currentName = getName();
@ -406,14 +360,8 @@ public class NodeItem<T> implements Node<T> {
public void removeParent() {
if (parent != null) {
Node<T> oldParent = parent;
Function<Node<T>, String> supplier = getNameSupplier();
parent = null;
oldParent.removeChild(this);
if (this.nameSupplier == null) {
// this is now a root node, so must provide a default name
// supplier
this.nameSupplier = supplier;
}
}
}

View file

@ -0,0 +1,107 @@
package net.kemitix.node;
import java.util.Optional;
import java.util.Set;
import java.util.stream.Collectors;
/**
* Utility class for {@link Node} items.
*
* @author pcampbell
*/
public final class Nodes {
private Nodes() {
}
/**
* Creates a new unnamed root node.
*
* @param data the data the node will contain
* @param <T> the type of the data
*
* @return the new node
*/
public static <T> Node<T> unnamedRoot(final T data) {
return new NodeItem<>(data);
}
/**
* Creates a new named root node.
*
* @param data the data the node will contain
* @param name the name of the node
* @param <T> the type of the data
*
* @return the new node
*/
public static <T> Node<T> namedRoot(final T data, final String name) {
return new NodeItem<>(data, name);
}
/**
* Creates a new unnamed child node.
*
* @param data the data the node will contain
* @param parent the parent of the node
* @param <T> the type of the data
*
* @return the new node
*/
public static <T> Node<T> unnamedChild(final T data, final Node<T> parent) {
return new NodeItem<>(data, parent);
}
/**
* Creates a new named child node.
*
* @param data the data the node will contain
* @param name the name of the node
* @param parent the parent of the node
* @param <T> the type of the data
*
* @return the new node
*/
public static <T> Node<T> namedChild(
final T data, final String name, final Node<T> parent) {
return new NodeItem<>(data, name, parent);
}
/**
* Creates an immutable copy of an existing node tree.
*
* @param root the root node of the source tree
* @param <T> the type of the data
*
* @return the immutable copy of the tree
*/
public static <T> Node<T> asImmutable(final Node<T> root) {
if (root.getParent().isPresent()) {
throw new IllegalArgumentException("source must be the root node");
}
final Set<Node<T>> children = getImmutableChildren(root);
return ImmutableNodeItem.newRoot(root.getData().orElse(null),
root.getName(), children);
}
private static <T> Set<Node<T>> getImmutableChildren(final Node<T> source) {
return source.getChildren()
.stream()
.map(Nodes::asImmutableChild)
.collect(Collectors.toSet());
}
private static <T> Node<T> asImmutableChild(
final Node<T> source) {
final Optional<Node<T>> sourceParent = source.getParent();
if (sourceParent.isPresent()) {
return ImmutableNodeItem.newChild(source.getData().orElse(null),
source.getName(), sourceParent.get(),
getImmutableChildren(source));
} else {
throw new IllegalArgumentException(
"source must not be the root node");
}
}
}

View file

@ -0,0 +1,454 @@
package net.kemitix.node;
import lombok.val;
import org.assertj.core.api.SoftAssertions;
import org.junit.Rule;
import org.junit.Test;
import org.junit.experimental.categories.Category;
import org.junit.rules.ExpectedException;
import java.util.Arrays;
import java.util.Collections;
import java.util.Optional;
import static org.assertj.core.api.Assertions.assertThat;
/**
* Test for {@link ImmutableNodeItem}.
*
* @author pcampbell
*/
public class ImmutableNodeItemTest {
private static final String IMMUTABLE_OBJECT = "Immutable object";
@Rule
public ExpectedException exception = ExpectedException.none();
private Node<String> immutableNode;
private void expectImmutableException() {
exception.expect(UnsupportedOperationException.class);
exception.expectMessage(IMMUTABLE_OBJECT);
}
@Test
public void getDataReturnsData() {
//given
val data = "this immutableNode data";
//when
immutableNode = Nodes.asImmutable(Nodes.unnamedRoot(data));
//then
assertThat(immutableNode.getData()).as(
"can get the data from a immutableNode").
contains(data);
}
@Test
public void canCreateAnEmptyAndUnnamedNode() {
//when
immutableNode = Nodes.asImmutable(Nodes.unnamedRoot(null));
//then
SoftAssertions softly = new SoftAssertions();
softly.assertThat(immutableNode.isEmpty())
.as("immutableNode is empty")
.isTrue();
softly.assertThat(immutableNode.isNamed())
.as("immutableNode is unnamed")
.isFalse();
softly.assertAll();
}
@Test
public void shouldThrowExceptionOnSetName() {
//given
immutableNode = Nodes.asImmutable(Nodes.unnamedRoot(null));
expectImmutableException();
//when
immutableNode.setName("named");
}
@Test
public void rootNodeShouldHaveNoParent() {
//given
immutableNode = Nodes.asImmutable(Nodes.unnamedRoot("data"));
//then
assertThat(immutableNode.getParent()).as(
"immutableNode created without a parent has no parent")
.isEmpty();
}
@Test
public void shouldContainImmutableCopyOfChild() {
//given
val parent = Nodes.unnamedRoot("root");
val child = Nodes.namedChild("child", "child", parent);
//when
immutableNode = Nodes.asImmutable(parent);
//then
val immutableChild = immutableNode.getChildByName("child");
assertThat(immutableChild).isNotSameAs(child);
assertThat(immutableChild.getName()).isEqualTo("child");
}
@Test
public void childShouldHaveImmutableParent() {
//given
val parent = Nodes.namedRoot("parent", "root");
Nodes.namedChild("subject", "child", parent);
//when
immutableNode = Nodes.asImmutable(parent);
//then
// get the immutable node's child's parent
val immutableChild = immutableNode.getChildByName("child");
final Optional<Node<String>> optionalParent
= immutableChild.getParent();
if (optionalParent.isPresent()) {
val p = optionalParent.get();
assertThat(p).hasFieldOrPropertyWithValue("name", "root")
.hasFieldOrPropertyWithValue("data",
Optional.of("parent"));
}
}
@Test
public void shouldNotBeAbleToAddChildToImmutableTree() {
//given
immutableNode = Nodes.asImmutable(Nodes.unnamedRoot("root"));
expectImmutableException();
//when
Nodes.unnamedChild("child", immutableNode);
}
@Test
public void shouldThrowExceptionWhenSetParent() {
//given
immutableNode = Nodes.asImmutable(Nodes.unnamedRoot("subject"));
expectImmutableException();
//when
immutableNode.setParent(null);
}
@Test
public void shouldThrowExceptionWhenAddingChild() {
//given
immutableNode = Nodes.asImmutable(Nodes.unnamedRoot("subject"));
expectImmutableException();
//when
immutableNode.addChild(Nodes.unnamedRoot("child"));
}
/**
* Test that we can walk a tree to the target node.
*/
@Test
@Category(NodeFindInPathTestsCategory.class)
public void shouldWalkTreeToNode() {
//given
val root = Nodes.unnamedRoot("root");
Nodes.namedChild("child", "child", Nodes.unnamedChild("parent", root));
immutableNode = Nodes.asImmutable(root);
//when
val result = immutableNode.findInPath(Arrays.asList("parent", "child"));
//then
assertThat(result.isPresent()).isTrue();
if (result.isPresent()) {
assertThat(result.get().getName()).isEqualTo("child");
}
}
/**
* Test that we get an empty {@link Optional} when walking a path that
* doesn't exist.
*/
@Test
@Category(NodeFindInPathTestsCategory.class)
public void shouldNotFindNonExistentChildNode() {
//given
val root = Nodes.unnamedRoot("root");
Nodes.unnamedChild("child", Nodes.unnamedChild("parent", root));
immutableNode = Nodes.asImmutable(root);
//when
val result = immutableNode.findInPath(
Arrays.asList("parent", "no child"));
//then
assertThat(result.isPresent()).isFalse();
}
/**
* Test that when we pass null we get an exception.
*/
@Test
@Category(NodeFindInPathTestsCategory.class)
public void shouldThrowNEWhenWalkTreeNull() {
//given
immutableNode = Nodes.asImmutable(Nodes.unnamedRoot("subject"));
exception.expect(NullPointerException.class);
exception.expectMessage("path");
//when
immutableNode.findInPath(null);
}
/**
* Test that when we pass an empty path we get and empty {@link Optional} as
* a result.
*/
@Test
@Category(NodeFindInPathTestsCategory.class)
public void shouldReturnEmptyForEmptyWalkTreePath() {
//given
immutableNode = Nodes.asImmutable(Nodes.unnamedRoot("subject"));
//when
val result = immutableNode.findInPath(Collections.emptyList());
//then
assertThat(result).isEmpty();
}
/**
* Test that we can find a child of a immutableNode.
*/
@Test
public void shouldFindExistingChildNode() {
//given
val root = Nodes.unnamedRoot("root");
Nodes.unnamedChild("child", root);
immutableNode = Nodes.asImmutable(root);
//when
val result = immutableNode.findChild("child");
//then
assertThat(result.isPresent()).isTrue();
if (result.isPresent()) {
assertThat(result.get().getData()).contains("child");
}
}
/**
* Test that if we pass null we get an exception.
*/
@Test
public void findOrCreateChildShouldThrowNPEFWhenChildIsNull() {
//given
immutableNode = Nodes.asImmutable(Nodes.unnamedRoot("subject"));
exception.expect(NullPointerException.class);
exception.expectMessage("child");
//when
immutableNode.findOrCreateChild(null);
}
/**
* Test that we throw an exception when passed null.
*/
@Test
public void getChildShouldThrowNPEWhenThereIsNoChild() {
//given
immutableNode = Nodes.asImmutable(Nodes.unnamedRoot("data"));
exception.expect(NullPointerException.class);
exception.expectMessage("child");
//when
immutableNode.findChild(null);
}
@Test
public void getChildNamedFindsChild() {
//given
val root = Nodes.namedRoot("root data", "root");
val alpha = Nodes.namedRoot("alpha data", "alpha");
val beta = Nodes.namedRoot("beta data", "beta");
root.addChild(alpha);
root.addChild(beta);
immutableNode = Nodes.asImmutable(root);
//when
val result = immutableNode.getChildByName("alpha");
//then
assertThat(result.getName()).isEqualTo(alpha.getName());
}
@Test
public void getChildNamedFindsNothing() {
//given
val root = Nodes.namedRoot("root data", "root");
val alpha = Nodes.namedRoot("alpha data", "alpha");
val beta = Nodes.namedRoot("beta data", "beta");
root.addChild(alpha);
root.addChild(beta);
exception.expect(NodeException.class);
exception.expectMessage("Named child not found");
immutableNode = Nodes.asImmutable(root);
//when
immutableNode.getChildByName("gamma");
}
@Test
public void removingParentThrowsException() {
//given
immutableNode = Nodes.asImmutable(Nodes.unnamedRoot(null));
expectImmutableException();
//when
immutableNode.removeParent();
}
@Test
public void findChildNamedShouldThrowNPEWhenNameIsNull() {
//given
exception.expect(NullPointerException.class);
exception.expectMessage("name");
immutableNode = Nodes.asImmutable(Nodes.unnamedRoot(null));
//when
immutableNode.findChildByName(null);
}
@Test
public void isNamedNull() {
//given
immutableNode = Nodes.asImmutable(Nodes.unnamedRoot(null));
//then
assertThat(immutableNode.isNamed()).isFalse();
}
@Test
public void isNamedEmpty() {
//given
immutableNode = Nodes.asImmutable(Nodes.namedRoot(null, ""));
//then
assertThat(immutableNode.isNamed()).isFalse();
}
@Test
public void isNamedNamed() {
//given
immutableNode = Nodes.asImmutable(Nodes.namedRoot(null, "named"));
//then
assertThat(immutableNode.isNamed()).isTrue();
}
@Test
public void removeChildThrowsExceptions() {
//given
immutableNode = Nodes.asImmutable(Nodes.unnamedRoot(null));
expectImmutableException();
//then
immutableNode.removeChild(null);
}
@Test
public void drawTreeIsCorrect() {
//given
val root = Nodes.namedRoot("root data", "root");
val bob = Nodes.namedChild("bob data", "bob", root);
val alice = Nodes.namedChild("alice data", "alice", root);
Nodes.namedChild("dave data", "dave", alice);
Nodes.unnamedChild("bob's child's data",
bob); // has no name and no children so no included
val kim = Nodes.unnamedChild("kim data", root); // nameless mother
Nodes.namedChild("lucy data", "lucy", kim);
immutableNode = Nodes.asImmutable(root);
//when
val tree = immutableNode.drawTree(0);
//then
String[] lines = tree.split("\n");
assertThat(lines).contains("[root]", "[ alice]", "[ dave]",
"[ (unnamed)]", "[ lucy]", "[ bob]");
assertThat(lines).containsSubsequence("[root]", "[ alice]", "[ dave]");
assertThat(lines).containsSubsequence("[root]", "[ (unnamed)]",
"[ lucy]");
assertThat(lines).containsSubsequence("[root]", "[ bob]");
}
@Test
public void setDataShouldThrowException() {
//given
immutableNode = Nodes.asImmutable(Nodes.unnamedRoot("initial"));
expectImmutableException();
//when
immutableNode.setData("updated");
}
@Test
public void createChildThrowsException() {
//given
immutableNode = Nodes.asImmutable(Nodes.unnamedRoot(null));
expectImmutableException();
//when
immutableNode.createChild("child data", "child name");
}
@Test
public void canGetChildWhenFound() {
//given
val root = Nodes.unnamedRoot("data");
val child = Nodes.namedChild("child data", "child name", root);
immutableNode = Nodes.asImmutable(root);
//when
val found = immutableNode.getChild("child data");
//then
assertThat(found.getName()).isEqualTo(child.getName());
}
@Test
public void canGetChildWhenNotFound() {
//given
exception.expect(NodeException.class);
exception.expectMessage("Child not found");
immutableNode = Nodes.asImmutable(Nodes.unnamedRoot("data"));
//when
immutableNode.getChild("child data");
}
@Test
public void canSafelyHandleFindChildWhenAChildHasNoData() {
//given
val root = Nodes.unnamedRoot("");
Nodes.unnamedChild(null, root);
immutableNode = Nodes.asImmutable(root);
//when
immutableNode.findChild("data");
}
@Test
public void createChildShouldThrowException() {
//given
immutableNode = Nodes.asImmutable(Nodes.unnamedRoot(""));
expectImmutableException();
//when
immutableNode.createChild("child");
}
@Test
public void createDescendantLineShouldThrowException() {
//given
immutableNode = Nodes.asImmutable(Nodes.unnamedRoot(""));
expectImmutableException();
//when
immutableNode.createDescendantLine(
Arrays.asList("child", "grandchild"));
}
@Test
public void insertInPathShouldThrowException() {
//given
immutableNode = Nodes.asImmutable(Nodes.unnamedRoot(""));
expectImmutableException();
//when
immutableNode.insertInPath(null, "");
}
@Test
public void findOrCreateChildShouldReturnChildWhenChildIsFound() {
//given
val root = Nodes.unnamedRoot("");
Nodes.namedChild("child", "child", root);
immutableNode = Nodes.asImmutable(root);
//when
val found = immutableNode.findOrCreateChild("child");
assertThat(found).extracting(Node::getName).contains("child");
}
@Test
public void findOrCreateChildShouldThrowExceptionWhenChildNotFound() {
//given
immutableNode = Nodes.asImmutable(Nodes.unnamedRoot(""));
expectImmutableException();
//when
immutableNode.findOrCreateChild("child");
}
}

View file

@ -0,0 +1,9 @@
package net.kemitix.node;
/**
* Category marker for tests relating to implementations of Node.findInPath(...).
*
* @author Paul Campbell
*/
public interface NodeFindInPathTestsCategory {
}

View file

@ -4,17 +4,14 @@ import lombok.val;
import org.assertj.core.api.SoftAssertions;
import org.junit.Rule;
import org.junit.Test;
import org.junit.experimental.categories.Category;
import org.junit.rules.ExpectedException;
import static org.assertj.core.api.Assertions.assertThat;
import java.time.LocalDate;
import java.time.format.DateTimeFormatter;
import java.util.Arrays;
import java.util.Collections;
import java.util.Optional;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.function.Function;
import static org.assertj.core.api.Assertions.assertThat;
/**
* Test for {@link NodeItem}.
@ -33,7 +30,7 @@ public class NodeItemTest {
//given
val data = "this node data";
//when
node = new NodeItem<>(data);
node = Nodes.unnamedRoot(data);
//then
assertThat(node.getData()).as("can get the data from a node").
contains(data);
@ -42,7 +39,7 @@ public class NodeItemTest {
@Test
public void canCreateAnEmptyAndUnnamedNode() {
//when
node = new NodeItem<>(null);
node = Nodes.unnamedRoot(null);
//then
SoftAssertions softly = new SoftAssertions();
softly.assertThat(node.isEmpty()).as("node is empty").isTrue();
@ -50,21 +47,10 @@ public class NodeItemTest {
softly.assertAll();
}
@Test
public void canCreateNodeWithParentAndCustomNameSupplier() {
//given
node = new NodeItem<>(null, n -> "root name supplier");
//when
val child = new NodeItem<String>(null, n -> "overridden", node);
//then
assertThat(child.getName()).isEqualTo("overridden");
assertThat(child.getParent()).contains(node);
}
@Test
public void canSetName() {
//given
node = new NodeItem<>(null);
node = Nodes.unnamedRoot(null);
//when
node.setName("named");
//then
@ -77,7 +63,7 @@ public class NodeItemTest {
@Test
public void shouldHaveNullForDefaultParent() {
//given
node = new NodeItem<>("data");
node = Nodes.unnamedRoot("data");
//then
assertThat(node.getParent()).as(
"node created without a parent has no parent").isEmpty();
@ -89,9 +75,9 @@ public class NodeItemTest {
@Test
public void shouldReturnNodeParent() {
//given
val parent = new NodeItem<String>("parent");
val parent = Nodes.unnamedRoot("parent");
//when
node = new NodeItem<>("subject", parent);
node = Nodes.unnamedChild("subject", parent);
//then
assertThat(node.getParent()).as(
"node created with a parent can return the parent")
@ -105,8 +91,8 @@ public class NodeItemTest {
@Test
public void setParentShouldThrowNodeExceptionWhenParentIsAChild() {
//given
node = new NodeItem<>("subject");
val child = new NodeItem<String>("child", node);
node = Nodes.unnamedRoot("subject");
val child = Nodes.unnamedChild("child", node);
exception.expect(NodeException.class);
exception.expectMessage("Parent is a descendant");
//when
@ -121,9 +107,9 @@ public class NodeItemTest {
@SuppressWarnings("unchecked")
public void shouldAddNewNodeAsChildToParent() {
//given
val parent = new NodeItem<String>("parent");
val parent = Nodes.unnamedRoot("parent");
//when
node = new NodeItem<>("subject", parent);
node = Nodes.unnamedChild("subject", parent);
//then
assertThat(parent.getChildren()).as(
"when a node is created with a parent, the parent has the new"
@ -136,8 +122,8 @@ public class NodeItemTest {
@Test
public void shouldReturnSetParent() {
//given
node = new NodeItem<>("subject");
val parent = new NodeItem<String>("parent");
node = Nodes.unnamedRoot("subject");
val parent = Nodes.unnamedRoot("parent");
//when
node.setParent(parent);
//then
@ -152,7 +138,7 @@ public class NodeItemTest {
@Test
public void shouldThrowNPEWhenSetParentNull() {
//given
node = new NodeItem<>("subject");
node = Nodes.unnamedRoot("subject");
exception.expect(NullPointerException.class);
exception.expectMessage("parent");
//when
@ -166,7 +152,7 @@ public class NodeItemTest {
@Test
public void setParentShouldThrowNodeExceptionWhenParentIsSelf() {
//given
node = new NodeItem<>("subject");
node = Nodes.unnamedRoot("subject");
exception.expect(NodeException.class);
exception.expectMessage("Parent is a descendant");
//when
@ -180,9 +166,9 @@ public class NodeItemTest {
@Test
public void shouldUpdateOldParentWhenNodeSetToNewParent() {
//given
node = new NodeItem<>("subject");
node = Nodes.unnamedRoot("subject");
val child = node.createChild("child");
val newParent = new NodeItem<String>("newParent");
val newParent = Nodes.unnamedRoot("newParent");
//when
child.setParent(newParent);
//then
@ -201,9 +187,9 @@ public class NodeItemTest {
@Test
public void shouldRemoveNodeFromOldParentWhenAddedAsChildToNewParent() {
//given
node = new NodeItem<>("subject");
node = Nodes.unnamedRoot("subject");
val child = node.createChild("child");
val newParent = new NodeItem<String>("newParent");
val newParent = Nodes.unnamedRoot("newParent");
//when
newParent.addChild(child);
//then
@ -223,7 +209,7 @@ public class NodeItemTest {
@Test
public void shouldThrowNPEWhenAddingNullAsChild() {
//given
node = new NodeItem<>("subject");
node = Nodes.unnamedRoot("subject");
exception.expect(NullPointerException.class);
exception.expectMessage("child");
//when
@ -237,8 +223,8 @@ public class NodeItemTest {
@SuppressWarnings("unchecked")
public void shouldReturnAddedChild() {
//given
node = new NodeItem<>("subject");
val child = new NodeItem<String>("child");
node = Nodes.unnamedRoot("subject");
val child = Nodes.unnamedRoot("child");
//when
node.addChild(child);
//then
@ -253,7 +239,7 @@ public class NodeItemTest {
@Test
public void addChildShouldThrowNodeExceptionWhenAddingANodeAsOwnChild() {
//given
node = new NodeItem<>("subject");
node = Nodes.unnamedRoot("subject");
exception.expect(NodeException.class);
exception.expectMessage("Child is an ancestor");
//then
@ -266,7 +252,7 @@ public class NodeItemTest {
@Test
public void addChildShouldThrowNodeExceptionWhenAddingSelfAsChild() {
//given
node = new NodeItem<>("subject");
node = Nodes.unnamedRoot("subject");
exception.expect(NodeException.class);
exception.expectMessage("Child is an ancestor");
//when
@ -280,8 +266,8 @@ public class NodeItemTest {
@Test
public void addChildShouldThrowNodeExceptionWhenChildIsParent() {
//given
val parent = new NodeItem<String>("parent");
node = new NodeItem<>("subject", parent);
val parent = Nodes.unnamedRoot("parent");
node = Nodes.unnamedChild("subject", parent);
exception.expect(NodeException.class);
exception.expectMessage("Child is an ancestor");
//when
@ -295,9 +281,9 @@ public class NodeItemTest {
@Test
public void addChildShouldThrowNodeExceptionWhenAddingGrandParentAsChild() {
//given
val grandParent = new NodeItem<String>("grandparent");
val parent = new NodeItem<String>("parent", grandParent);
node = new NodeItem<>("subject", parent);
val grandParent = Nodes.unnamedRoot("grandparent");
val parent = Nodes.unnamedChild("parent", grandParent);
node = Nodes.unnamedChild("subject", parent);
exception.expect(NodeException.class);
exception.expectMessage("Child is an ancestor");
//when
@ -310,8 +296,8 @@ public class NodeItemTest {
@Test
public void shouldSetParentOnChildWhenAddedAsChild() {
//given
node = new NodeItem<>("subject");
val child = new NodeItem<String>("child");
node = Nodes.unnamedRoot("subject");
val child = Nodes.unnamedRoot("child");
//when
node.addChild(child);
//then
@ -324,14 +310,15 @@ public class NodeItemTest {
* Test that we can walk a tree to the target node.
*/
@Test
@Category(NodeFindInPathTestsCategory.class)
public void shouldWalkTreeToNode() {
//given
val grandparent = "grandparent";
val grandParentNode = new NodeItem<String>(grandparent);
val grandParentNode = Nodes.unnamedRoot(grandparent);
val parent = "parent";
val parentNode = new NodeItem<String>(parent, grandParentNode);
val parentNode = Nodes.unnamedChild(parent, grandParentNode);
val subject = "subject";
node = new NodeItem<>(subject, parentNode);
node = Nodes.unnamedChild(subject, parentNode);
//when
val result = grandParentNode.findInPath(Arrays.asList(parent, subject));
//then
@ -349,12 +336,13 @@ public class NodeItemTest {
* doesn't exist.
*/
@Test
@Category(NodeFindInPathTestsCategory.class)
public void shouldNotFindNonExistentChildNode() {
//given
val parent = "parent";
val parentNode = new NodeItem<String>(parent);
val parentNode = Nodes.unnamedRoot(parent);
val subject = "subject";
node = new NodeItem<>(subject, parentNode);
node = Nodes.unnamedChild(subject, parentNode);
//when
val result = parentNode.findInPath(Arrays.asList(subject, "no child"));
//then
@ -367,9 +355,10 @@ public class NodeItemTest {
* Test that when we pass null we get an exception.
*/
@Test
@Category(NodeFindInPathTestsCategory.class)
public void shouldThrowNEWhenWalkTreeNull() {
//given
node = new NodeItem<>("subject");
node = Nodes.unnamedRoot("subject");
exception.expect(NullPointerException.class);
exception.expectMessage("path");
//when
@ -381,9 +370,10 @@ public class NodeItemTest {
* a result.
*/
@Test
@Category(NodeFindInPathTestsCategory.class)
public void shouldReturnEmptyForEmptyWalkTreePath() {
//given
node = new NodeItem<>("subject");
node = Nodes.unnamedRoot("subject");
//when
val result = node.findInPath(Collections.emptyList());
//then
@ -396,7 +386,7 @@ public class NodeItemTest {
@Test
public void shouldCreateDescendantNodes() {
//given
node = new NodeItem<>("subject");
node = Nodes.unnamedRoot("subject");
val alphaData = "alpha";
val betaData = "beta";
val gammaData = "gamma";
@ -445,7 +435,7 @@ public class NodeItemTest {
@Test
public void createDescendantLineShouldThrowNPEWhenDescendantsAreNull() {
//given
node = new NodeItem<>("subject");
node = Nodes.unnamedRoot("subject");
exception.expect(NullPointerException.class);
exception.expectMessage("descendants");
//when
@ -458,7 +448,7 @@ public class NodeItemTest {
@Test
public void shouldChangeNothingWhenCreateDescendantEmpty() {
//given
node = new NodeItem<>("subject");
node = Nodes.unnamedRoot("subject");
//when
node.createDescendantLine(Collections.emptyList());
//then
@ -473,9 +463,9 @@ public class NodeItemTest {
@Test
public void shouldFindExistingChildNode() {
//given
node = new NodeItem<>("subject");
node = Nodes.unnamedRoot("subject");
val childData = "child";
val child = new NodeItem<String>(childData, node);
val child = Nodes.unnamedChild(childData, node);
//when
val found = node.findOrCreateChild(childData);
//then
@ -490,7 +480,7 @@ public class NodeItemTest {
@Test
public void shouldFindCreateNewChildNode() {
//given
node = new NodeItem<>("subject");
node = Nodes.unnamedRoot("subject");
val childData = "child";
//when
val found = node.findOrCreateChild(childData);
@ -506,7 +496,7 @@ public class NodeItemTest {
@Test
public void findOrCreateChildShouldThrowNPEFWhenChildIsNull() {
//given
node = new NodeItem<>("subject");
node = Nodes.unnamedRoot("subject");
exception.expect(NullPointerException.class);
exception.expectMessage("child");
//when
@ -519,9 +509,9 @@ public class NodeItemTest {
@Test
public void shouldGetChild() {
//given
node = new NodeItem<>("subject");
node = Nodes.unnamedRoot("subject");
val childData = "child";
val child = new NodeItem<String>(childData);
val child = Nodes.unnamedRoot(childData);
node.addChild(child);
//when
val found = node.findChild(childData);
@ -541,7 +531,7 @@ public class NodeItemTest {
@Test
public void getChildShouldThrowNPEWhenThereIsNoChild() {
//given
node = new NodeItem<>("data");
node = Nodes.unnamedRoot("data");
exception.expect(NullPointerException.class);
exception.expectMessage("child");
//when
@ -555,7 +545,7 @@ public class NodeItemTest {
@Test
public void shouldCreateChild() {
//given
node = new NodeItem<>("subject");
node = Nodes.unnamedRoot("subject");
val childData = "child";
//when
val child = node.createChild(childData);
@ -580,82 +570,25 @@ public class NodeItemTest {
@Test
public void createChildShouldThrowNPEWhenChildIsNull() {
//given
node = new NodeItem<>("subject");
node = Nodes.unnamedRoot("subject");
exception.expect(NullPointerException.class);
exception.expectMessage("child");
//when
node.createChild(null);
}
@Test
public void getNameShouldBeCorrect() {
//given
node = new NodeItem<>("subject", n -> n.getData().get());
//then
assertThat(node.getName()).isEqualTo("subject");
}
@Test
public void getNameShouldUseParentNameSupplier() {
//given
val root = new NodeItem<String>("root", n -> n.getData().get());
node = new NodeItem<>("child", root);
//then
assertThat(node.getName()).isEqualTo("child");
}
@Test
public void getNameShouldReturnNameForNonStringData() {
val root = new NodeItem<LocalDate>(LocalDate.parse("2016-05-23"), n -> {
if (n.isEmpty()) {
return null;
}
return n.getData().get().format(DateTimeFormatter.BASIC_ISO_DATE);
});
//then
assertThat(root.getName()).isEqualTo("20160523");
}
@Test
public void getNameShouldUseClosestNameSupplier() {
node = new NodeItem<>("root", n -> n.getData().get());
val child = new NodeItem<String>("child", Object::toString);
node.addChild(child);
val grandChild = new NodeItem<>("grandchild", child);
//then
assertThat(node.getName()).isEqualTo("root");
assertThat(child.getName()).isNotEqualTo("child");
assertThat(grandChild.getName()).isNotEqualTo("grandchild");
}
@Test
public void getNameShouldWorkWithoutNameSupplier() {
node = new NodeItem<>(null, "root");
val namedchild = new NodeItem<>("named", "Alice", node);
//then
assertThat(node.getName()).isEqualTo("root");
assertThat(namedchild.getName()).isEqualTo("Alice");
}
@Test
public void canCreateRootNodeWithoutData() {
node = new NodeItem<>(null, "empty");
assertThat(node.getData()).isEmpty();
}
@Test
public void canCreateRootNodeWithoutDataButWithNameSupplier() {
node = new NodeItem<>(null);
node = Nodes.namedRoot(null, "empty");
assertThat(node.getData()).isEmpty();
}
@Test
public void getChildNamedFindsChild() {
//given
node = new NodeItem<>(null, "root");
val alpha = new NodeItem<String>(null, "alpha");
val beta = new NodeItem<String>(null, "beta");
node = Nodes.namedRoot("root data", "root");
val alpha = Nodes.namedRoot("alpha data", "alpha");
val beta = Nodes.namedRoot("beta data", "beta");
node.addChild(alpha);
node.addChild(beta);
//when
@ -667,9 +600,9 @@ public class NodeItemTest {
@Test
public void getChildNamedFindsNothing() {
//given
node = new NodeItem<>(null, "root");
val alpha = new NodeItem<String>(null, "alpha");
val beta = new NodeItem<String>(null, "beta");
node = Nodes.namedRoot("root data", "root");
val alpha = Nodes.namedRoot("alpha data", "alpha");
val beta = Nodes.namedRoot("beta data", "beta");
node.addChild(alpha);
node.addChild(beta);
exception.expect(NodeException.class);
@ -681,10 +614,10 @@ public class NodeItemTest {
@Test
public void nodeNamesAreUniqueWithinAParent() {
//given
node = new NodeItem<>(null, "root");
val alpha = new NodeItem<String>(null, "alpha");
node = Nodes.namedRoot("root data", "root");
val alpha = Nodes.namedRoot("alpha data", "alpha");
node.addChild(alpha);
val beta = new NodeItem<String>(null, "alpha");
val beta = Nodes.namedRoot("beta data", "alpha");
exception.expect(NodeException.class);
exception.expectMessage("Node with that name already exists here");
//when
@ -694,8 +627,8 @@ public class NodeItemTest {
@Test
public void canPlaceNodeInTreeByPathNames() {
//given
node = new NodeItem<>(null, "root"); // create a root
val four = new NodeItem<String>("data", "four");
node = Nodes.namedRoot("root data", "root"); // create a root
val four = Nodes.namedRoot("data", "four");
//when
node.insertInPath(four, "one", "two", "three");
//then
@ -717,9 +650,9 @@ public class NodeItemTest {
@SuppressWarnings("unchecked")
public void canPlaceInTreeUnderExistingNode() {
//given
node = new NodeItem<>(null, "root");
val child = new NodeItem<String>("child data", "child");
val grandchild = new NodeItem<String>("grandchild data", "grandchild");
node = Nodes.namedRoot(null, "root");
val child = Nodes.namedRoot("child data", "child");
val grandchild = Nodes.namedRoot("grandchild data", "grandchild");
//when
node.insertInPath(child); // as root/child
node.insertInPath(grandchild, "child"); // as root/child/grandchild
@ -734,9 +667,9 @@ public class NodeItemTest {
@SuppressWarnings("unchecked")
public void canPlaceInTreeAboveExistingNode() {
//given
node = new NodeItem<>(null, "root");
val child = new NodeItem<String>("child data", "child");
val grandchild = new NodeItem<String>("grandchild data", "grandchild");
node = Nodes.namedRoot(null, "root");
val child = Nodes.namedRoot("child data", "child");
val grandchild = Nodes.namedRoot("grandchild data", "grandchild");
//when
node.insertInPath(grandchild, "child");
node.insertInPath(child);
@ -752,7 +685,7 @@ public class NodeItemTest {
@Test
public void removingParentFromNodeWithNoParentIsNoop() {
//given
node = new NodeItem<>(null);
node = Nodes.unnamedRoot(null);
//when
node.removeParent();
}
@ -760,8 +693,8 @@ public class NodeItemTest {
@Test
public void removingParentFromNodeWithParentRemovesParent() {
//given
node = new NodeItem<>(null);
val child = new NodeItem<String>(null, node);
node = Nodes.unnamedRoot(null);
val child = Nodes.unnamedChild("child data", node);
//when
child.removeParent();
//then
@ -775,22 +708,22 @@ public class NodeItemTest {
exception.expect(NodeException.class);
exception.expectMessage(
"A non-empty node named 'grandchild' already exists here");
node = new NodeItem<>(null);
val child = new NodeItem<String>(null, "child", node);
new NodeItem<>("data", "grandchild", child);
node = Nodes.unnamedRoot(null);
val child = Nodes.namedChild("child data", "child", node);
Nodes.namedChild("data", "grandchild", child);
// root -> child -> grandchild
// only grandchild has data
//when
// attempt to add another node called 'grandchild' to 'child'
node.insertInPath(new NodeItem<>("cuckoo", "grandchild"), "child");
node.insertInPath(Nodes.namedRoot("cuckoo", "grandchild"), "child");
}
@Test
@SuppressWarnings("unchecked")
public void placeNodeInTreeWhenAddedNodeIsUnnamed() {
//given
node = new NodeItem<>(null);
final Node<String> newNode = new NodeItem<>(null);
node = Nodes.unnamedRoot(null);
final Node<String> newNode = Nodes.unnamedRoot(null);
//when
node.insertInPath(newNode);
//then
@ -801,12 +734,12 @@ public class NodeItemTest {
@SuppressWarnings("unchecked")
public void placeNodeInTreeWhenEmptyChildWithTargetNameExists() {
//given
node = new NodeItem<>(null);
final NodeItem<String> child = new NodeItem<>(null, "child");
final NodeItem<String> target = new NodeItem<>(null, "target");
node = Nodes.unnamedRoot(null);
final Node<String> child = Nodes.namedRoot(null, "child");
final Node<String> target = Nodes.namedRoot(null, "target");
node.addChild(child);
child.addChild(target);
final NodeItem<String> addMe = new NodeItem<>("I'm new", "target");
val addMe = Nodes.namedRoot("I'm new", "target");
assertThat(addMe.getParent()).isEmpty();
assertThat(child.getChildByName("target").isEmpty()).as(
"target starts empty").isTrue();
@ -823,7 +756,7 @@ public class NodeItemTest {
//given
exception.expect(NullPointerException.class);
exception.expectMessage("name");
node = new NodeItem<>(null);
node = Nodes.unnamedRoot(null);
//when
node.findChildByName(null);
}
@ -831,7 +764,7 @@ public class NodeItemTest {
@Test
public void isNamedNull() {
//given
node = new NodeItem<>(null);
node = Nodes.unnamedRoot(null);
//then
assertThat(node.isNamed()).isFalse();
}
@ -839,7 +772,7 @@ public class NodeItemTest {
@Test
public void isNamedEmpty() {
//given
node = new NodeItem<>(null, "");
node = Nodes.namedRoot(null, "");
//then
assertThat(node.isNamed()).isFalse();
}
@ -847,32 +780,16 @@ public class NodeItemTest {
@Test
public void isNamedNamed() {
//given
node = new NodeItem<>(null, "named");
node = Nodes.namedRoot(null, "named");
//then
assertThat(node.isNamed()).isTrue();
}
@Test
public void removeParentNodeProvidesSameNameSupplier() {
// once a node has it's parent removed it should provide a default name
// provider
//given
node = new NodeItem<>("data", n -> n.getData().get());
final NodeItem<String> child = new NodeItem<>("other", node);
assertThat(node.getName()).as("initial root name").isEqualTo("data");
assertThat(child.getName()).as("initial child name").isEqualTo("other");
//when
child.removeParent();
//then
assertThat(node.getName()).as("final root name").isEqualTo("data");
assertThat(child.getName()).as("final child name").isEqualTo("other");
}
@Test
@SuppressWarnings("unchecked")
public void removeChildRemovesTheChild() {
//given
node = new NodeItem<>(null);
node = Nodes.unnamedRoot(null);
Node<String> child = node.createChild("child");
assertThat(node.getChildren()).containsExactly(child);
//then
@ -885,13 +802,14 @@ public class NodeItemTest {
@Test
public void drawTreeIsCorrect() {
//given
node = new NodeItem<>(null, "root");
val bob = new NodeItem<String>(null, "bob", node);
val alice = new NodeItem<String>(null, "alice", node);
new NodeItem<>(null, "dave", alice);
new NodeItem<>(null, bob); // has no name and no children so no included
val kim = new NodeItem<String>(null, node); // nameless mother
new NodeItem<>(null, "lucy", kim);
node = Nodes.namedRoot(null, "root");
val bob = Nodes.namedChild("bob data", "bob", node);
val alice = Nodes.namedChild("alice data", "alice", node);
Nodes.namedChild("dave data", "dave", alice);
Nodes.unnamedChild("bob's child's data",
bob); // has no name and no children so no included
val kim = Nodes.unnamedChild("kim data", node); // nameless mother
Nodes.namedChild("lucy data", "lucy", kim);
//when
val tree = node.drawTree(0);
//then
@ -907,7 +825,7 @@ public class NodeItemTest {
@Test
public void canChangeNodeData() {
//given
node = new NodeItem<>("initial");
node = Nodes.unnamedRoot("initial");
//when
node.setData("updated");
//then
@ -918,7 +836,7 @@ public class NodeItemTest {
@SuppressWarnings("unchecked")
public void canCreateNamedChild() {
//given
node = new NodeItem<>(null);
node = Nodes.unnamedRoot(null);
//when
Node<String> child = node.createChild("child data", "child name");
//then
@ -930,10 +848,10 @@ public class NodeItemTest {
@Test
public void canGetChildWhenFound() {
//given
node = new NodeItem<>("data");
Node<String> child = new NodeItem<>("child data", "child name", node);
node = Nodes.unnamedRoot("data");
val child = Nodes.namedChild("child data", "child name", node);
//when
Node<String> found = node.getChild("child data");
val found = node.getChild("child data");
//then
assertThat(found).isSameAs(child);
}
@ -943,7 +861,7 @@ public class NodeItemTest {
//given
exception.expect(NodeException.class);
exception.expectMessage("Child not found");
node = new NodeItem<>("data");
node = Nodes.unnamedRoot("data");
//when
node.getChild("child data");
}
@ -952,99 +870,19 @@ public class NodeItemTest {
@SuppressWarnings("unchecked")
public void constructorWithNameSupplierAndParentBeChildOfParent() {
//given
node = new NodeItem<>(null);
node = Nodes.unnamedRoot(null);
//when
NodeItem<String> child = new NodeItem<>(null, node);
val child = Nodes.unnamedChild("child data", node);
//then
assertThat(child.getParent()).contains(node);
assertThat(node.getChildren()).containsExactly(child);
}
@Test
@SuppressWarnings("unchecked")
public void removeParentCopiesRootNameSupplier() {
//given
node = new NodeItem<>("root data", n -> "root supplier");
val child = new NodeItem<>("child data", node);
assertThat(child.getName()).isEqualTo("root supplier");
//when
child.removeParent();
//then
assertThat(child.getName()).isEqualTo("root supplier");
}
@Test
@SuppressWarnings("unchecked")
public void removeParentDoesNotReplaceLocalNameSupplier() {
//given
node = new NodeItem<>("root data", n -> "root supplier");
val child = new NodeItem<>("child data", n -> "local supplier", node);
assertThat(child.getName()).isEqualTo("local supplier");
//when
child.removeParent();
//then
assertThat(child.getName()).isEqualTo("local supplier");
}
@Test
public void setNameToNullRevertsToParentNameSupplier() {
//given
node = new NodeItem<>(null, n -> "root supplier");
val child = new NodeItem<String>(null, "child name", node);
assertThat(child.getName()).isEqualTo("child name");
//when
child.setName(null);
//then
assertThat(child.getName()).isEqualTo("root supplier");
}
@Test
public void getNameWithNameSupplierIsRecalculatedEachCall() {
val counter = new AtomicInteger(0);
node = new NodeItem<>(null,
n -> Integer.toString(counter.incrementAndGet()));
//then
assertThat(node.getName()).isNotEqualTo(node.getName());
}
@Test
public void isNamedWithNameSupplierIsRecalculatedEachCall() {
val counter = new AtomicInteger(0);
node = new NodeItem<>(null, n -> {
// alternate between even numbers and nulls: null, 2, null, 4, null
final int i = counter.incrementAndGet();
if (i % 2 == 0) {
return Integer.toString(i);
}
return null;
});
//then
assertThat(node.isNamed()).isFalse();
assertThat(node.isNamed()).isTrue();
}
@Test
public void canUseNameSupplierToBuildFullPath() {
//given
final Function<Node<String>, String> pathNameSupplier = node -> {
Optional<Node<String>> parent = node.getParent();
if (parent.isPresent()) {
return parent.get().getName() + "/" + node.getData().get();
}
return "";
};
node = new NodeItem<>(null, pathNameSupplier);
val child = new NodeItem<String>("child", node);
val grandchild = new NodeItem<String>("grandchild", child);
//then
assertThat(grandchild.getName()).isEqualTo("/child/grandchild");
}
@Test
public void canSafelyHandleFindChildWhenAChildHasNoData() {
//given
node = new NodeItem<>(null);
new NodeItem<>(null, node);
node = Nodes.unnamedRoot(null);
Nodes.unnamedChild(null, node);
//when
node.findChild("data");
}

View file

@ -0,0 +1,62 @@
package net.kemitix.node;
import lombok.val;
import org.assertj.core.api.SoftAssertions;
import org.junit.Test;
import static net.trajano.commons.testing.UtilityClassTestUtil
.assertUtilityClassWellDefined;
/**
* Tests for {@link Nodes}.
*
* @author pcampbell
*/
public class NodesTest {
@Test
public void shouldBeValidUtilityClass() throws Exception {
assertUtilityClassWellDefined(Nodes.class);
}
@Test
public void shouldCreateUnnamedRoot() throws Exception {
val node = Nodes.unnamedRoot("data");
SoftAssertions softly = new SoftAssertions();
softly.assertThat(node.getData()).contains("data");
softly.assertThat(node.getName()).isNull();
softly.assertAll();
}
@Test
public void shouldCreateNamedRoot() throws Exception {
val node = Nodes.namedRoot("data", "name");
SoftAssertions softly = new SoftAssertions();
softly.assertThat(node.getData()).contains("data");
softly.assertThat(node.getName()).isEqualTo("name");
softly.assertAll();
}
@Test
public void shouldCreateUnnamedChild() throws Exception {
val parent = Nodes.unnamedRoot("root");
val node = Nodes.unnamedChild("data", parent);
SoftAssertions softly = new SoftAssertions();
softly.assertThat(node.getData()).contains("data");
softly.assertThat(node.getName()).isNull();
softly.assertThat(node.getParent()).contains(parent);
softly.assertAll();
}
@Test
public void shouldCreateNamedChild() throws Exception {
val parent = Nodes.unnamedRoot("root");
val node = Nodes.namedChild("data", "child", parent);
SoftAssertions softly = new SoftAssertions();
softly.assertThat(node.getData()).contains("data");
softly.assertThat(node.getName()).isEqualTo("child");
softly.assertThat(node.getParent()).contains(parent);
softly.assertAll();
}
}