Commit 728e74b1 authored by Nico Mack's avatar Nico Mack

Implemented concept of colour scales

parent 3a175449
......@@ -2,6 +2,7 @@ ABOVE_ELEMENT=above
BELOW_ELEMENT=below
BLINK_ON_OUT_OF_RANGE_NODE=blinkOnOutOfRange
CAPPED_DISPLAY_NODE=cappedDisplay
COLOUR_SCALE_NODE=colourScale
FACE_IS_TOUCHABLE_NODE=faceIsTouchable
ITEMS_VARIABLE_NODE=itemsVariable
HTML_TEMPLATE_NODE=htmlTemplate
......@@ -9,13 +10,14 @@ LABEL_NODE=label
LOWER_BOUND_RADIUS_NODE=lowerBoundRadius
LOWER_BOUND_VARIABLE_NODE=lowerBoundVariable
LOWER_STOP_ANGLE_NODE=lowerStopAngle
MAPPING_NODE=mapping
MAXIMUM_VALUE_NODE=maximumValue
MINIMUM_VALUE_NODE=minimumValue
MODIFY_VALUE_ON_ROTATION_NODE=modifyValueOnRotation
MULTITURN_NODE=multiTurn
RADIAL_LAYOUT_NODE=radialLayout
REFERENCE_NODE=reference
RELATIVE_NODE=relative
SCALES_NODE=scales
SELECTED_ITEMS_VARIABLE_NODE=selectedItemsVariable
SELECTED_RADIUS_NODE=selectedRadius
STEP_SIZE_NODE=stepSize
......
......@@ -31,6 +31,7 @@ public class CpsNamespace extends NLS {
public static String BLINK_ON_OUT_OF_RANGE_NODE;
public static String CAPPED_DISPLAY_NODE;
public static String COLOUR_SCALE_NODE;
public static String FACE_IS_TOUCHABLE_NODE;
public static String HTML_TEMPLATE_NODE;
......@@ -43,16 +44,19 @@ public class CpsNamespace extends NLS {
public static String LOWER_BOUND_VARIABLE_NODE;
public static String LOWER_STOP_ANGLE_NODE;
public static String MAPPING_NODE;
public static String MAXIMUM_VALUE_NODE;
public static String MINIMUM_VALUE_NODE;
public static String MODIFY_VALUE_ON_ROTATION_NODE;
public static String MULTITURN_NODE;
public static String RADIAL_LAYOUT_NODE;
public static String REFERENCE_NODE;
public static String RELATIVE_NODE;
public static String SCALES_NODE;
public static String SELECTED_ITEMS_VARIABLE_NODE;
public static String SELECTED_RADIUS_NODE;
public static String STEP_SIZE_NODE;
......
......@@ -23,7 +23,7 @@ package lu.list.itis.dkd.tui.utility;
// * Class Definition and Members *
// ***************************************************************************
public class ValueRange<T> {
public class ValueRange<T extends Comparable<T>> implements Comparable<T> {
private T lowerValue;
private T upperValue;
......@@ -42,6 +42,13 @@ public class ValueRange<T> {
// ***************************************************************************
// * Class Body
// ***************************************************************************
// ---------------------------------------------------------------------------
@SuppressWarnings("unchecked")
public Class<T> getClassOfValues() {
return (Class<T>) this.lowerValue.getClass();
}
// ---------------------------------------------------------------------------
/**
* Simple getter method for lowerValue.
......@@ -92,4 +99,37 @@ public class ValueRange<T> {
this.upperValue = upperValue;
}
// ---------------------------------------------------------------------------
/** {@inheritDoc} */
// ---------------------------------------------------------------------------
@Override
public int compareTo(T candidate) {
int comparison = candidate.compareTo(lowerValue);
if (comparison >= 0) {
comparison = candidate.compareTo(upperValue);
if (comparison <= 0) {
comparison = 0;
}
}
return comparison;
}
// ---------------------------------------------------------------------------
@Override
public String toString() {
StringBuilder builder = new StringBuilder("[");
builder.append((lowerValue != null) ? lowerValue : "null");
builder.append(" <=> ");
builder.append((upperValue != null) ? upperValue : "null");
builder.append("]");
return builder.toString();
}
// ---------------------------------------------------------------------------
// ***************************************************************************
// * End of class *
// ***************************************************************************
// ---------------------------------------------------------------------------
}
/**
* Copyright Luxembourg Institute of Science and Technology, 2018. All rights reserved. If you wish
* to use this code for any purpose, please contact the author(s).
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR
* IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND
* FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
* CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
* DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
* WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY
* WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
package lu.list.itis.dkd.tui.utility.scales;
import lu.list.itis.dkd.tui.utility.ValueRange;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.awt.Color;
/**
* @author Nico Mack [nico.mack@list.lu]
* @since 2.5
* @version 2.5.0
*/
// ***************************************************************************
// * Class Definition and Members *
// ***************************************************************************
public class ColourMapping<T extends Comparable<T>> {
private Color colour;
private ValueRange<T> range;
// ***************************************************************************
// * Constants *
// ***************************************************************************
private static final Logger LOGGER = LoggerFactory.getLogger(ColourMapping.class.getSimpleName());
// ---------------------------------------------------------------------------
// ***************************************************************************
// * Constructor(s) *
// ***************************************************************************
// ---------------------------------------------------------------------------
public ColourMapping(ValueRange<T> range, Color colour) {
this.range = range;
this.colour = colour;
}
// ---------------------------------------------------------------------------
// ***************************************************************************
// * Class Body *
// ***************************************************************************
// ---------------------------------------------------------------------------
public Class<T> getMappedClass() {
return this.range.getClassOfValues();
}
// ---------------------------------------------------------------------------
/**
* Simple getter method for colour.
*
* @return The value of colour.
*/
// ---------------------------------------------------------------------------
public Color getColour() {
return colour;
}
// ---------------------------------------------------------------------------
/**
* Simple setter method for colour.
*
* @param colour
* The value to set colour to.
*/
// ---------------------------------------------------------------------------
public void setColour(Color colour) {
this.colour = colour;
}
// ---------------------------------------------------------------------------
/**
* Simple getter method for range.
*
* @return The value of range.
*/
// ---------------------------------------------------------------------------
public ValueRange<T> getRange() {
return range;
}
// ---------------------------------------------------------------------------
/**
* Simple setter method for range.
*
* @param range
* The value to set range to.
*/
// ---------------------------------------------------------------------------
public void setRange(ValueRange<T> range) {
this.range = range;
}
// ---------------------------------------------------------------------------
/**
* Checks whether the specified value is inside this mappings' range.
*
* @param value
* specifies the value to be checked.
* @return <code>true</code> if specified value falls within this mappings' value range,
* <code>false</code> otherwise
*/
// ---------------------------------------------------------------------------
public boolean inRange(T value) {
boolean inRange = (range.compareTo(value) == 0);
if (LOGGER.isTraceEnabled() && inRange) {
LOGGER.trace("{} in range of {}", value, range);
}
return inRange;
}
// ---------------------------------------------------------------------------
// ***************************************************************************
// * End of class *
// ***************************************************************************
// ---------------------------------------------------------------------------
}
/**
* Copyright Luxembourg Institute of Science and Technology, 2018. All rights reserved. If you wish
* to use this code for any purpose, please contact the author(s).
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR
* IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND
* FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
* CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
* DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
* WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY
* WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
package lu.list.itis.dkd.tui.utility.scales;
import lu.list.itis.dkd.dbc.annotation.Nullable;
import lu.list.itis.dkd.tui.bootstrapping.BootstrappingUtils;
import lu.list.itis.dkd.tui.exception.BuildException;
import lu.list.itis.dkd.tui.utility.ColorFactory;
import lu.list.itis.dkd.tui.utility.CpsNamespace;
import lu.list.itis.dkd.tui.utility.Externalization;
import lu.list.itis.dkd.tui.utility.StringUtils;
import lu.list.itis.dkd.tui.utility.ValueRange;
import org.jdom2.Element;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.awt.Color;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
/**
* @author Nico Mack [nico.mack@list.lu]
* @since 2.5
* @version 2.5.0
*/
// ***************************************************************************
// * Class Definition and Members *
// ***************************************************************************
public class ColourScale<T extends Comparable<T>> {
private String name;
private List<ColourMapping<T>> mappings;
private Color defaultColour;
// ***************************************************************************
// * Constants *
// ***************************************************************************
private static final String AUTO = "auto"; //$NON-NLS-1$
private static final Logger LOGGER = LoggerFactory.getLogger(ColourScale.class.getSimpleName());
// ---------------------------------------------------------------------------
// ***************************************************************************
// * Constructor(s) *
// ***************************************************************************
// ---------------------------------------------------------------------------
public ColourScale() {
this.mappings = new ArrayList<>();
this.defaultColour = Color.WHITE;
}
// ---------------------------------------------------------------------------
public ColourScale(Element rootElement) throws BuildException {
this.mappings = new ArrayList<>();
this.buildFromBootstrap(rootElement);
}
// ---------------------------------------------------------------------------
// ***************************************************************************
// * Primitives *
// ***************************************************************************
// ---------------------------------------------------------------------------
private void buildFromBootstrap(@Nullable Element rootElement) throws BuildException {
name = BootstrappingUtils.getAttributeAsString(rootElement, Externalization.NAME_NODE, BootstrappingUtils.MANDATORY, null);
defaultColour = new Color(StringUtils.getIntegerValue(BootstrappingUtils.getAttributeAsString(rootElement, Externalization.DEFAULT_NODE, BootstrappingUtils.OPTIONAL, "0xFFFFFF")));
List<Element> mappingElements = rootElement.getChildren(CpsNamespace.MAPPING_NODE);
if ((mappingElements != null) && !mappingElements.isEmpty()) {
List<Color> automaticColours = ColorFactory.makeRainbowColours(mappingElements.size());
Iterator<Color> colourIterator = automaticColours.iterator();
for (Element mapping : mappingElements) {
this.mappings.add(this.buildValueRange(mapping, colourIterator.next()));
}
}
}
// ---------------------------------------------------------------------------
@SuppressWarnings("unchecked")
private ColourMapping<T> buildValueRange(@Nullable Element rootElement, Color automaticColour) throws BuildException {
ColourMapping<T> mapping;
double lowerBound = BootstrappingUtils.getAttributeAsDouble(rootElement, Externalization.LOWER_BOUND_NODE, BootstrappingUtils.MANDATORY, null);
double upperBound = BootstrappingUtils.getAttributeAsDouble(rootElement, Externalization.UPPER_BOUND_NODE, BootstrappingUtils.MANDATORY, null);
String colourNode = BootstrappingUtils.getAttributeAsString(rootElement, Externalization.COLOUR_NODE, BootstrappingUtils.MANDATORY, null);
Color colour;
if (AUTO.equals(colourNode)) {
colour = automaticColour;
} else {
colour = new Color(StringUtils.getIntegerValue(colourNode));
}
ValueRange<Double> range = new ValueRange<>(lowerBound, upperBound);
mapping = (ColourMapping<T>) new ColourMapping<>(range, colour);
return mapping;
}
// ---------------------------------------------------------------------------
// ***************************************************************************
// * Class Body *
// ***************************************************************************
// ---------------------------------------------------------------------------
public String getName() {
return this.name;
}
// ---------------------------------------------------------------------------
public boolean addMapping(ColourMapping<T> mapping) {
return this.mappings.add(mapping);
}
// ---------------------------------------------------------------------------
/**
* Returns the matching colour for the specified value if a corresponding colour mapping is
* available for this scale. If no such mapping is found, then this scales' default colour is
* returned.
*
* @param value
* specifies the value to get matching colour for.
* @return the colour defined by this scale for the specified value.
*/
// ---------------------------------------------------------------------------
public Color getColourFor(T value) {
for (ColourMapping<T> mapping : mappings) {
if (mapping.inRange(value)) {
return mapping.getColour();
}
}
return this.defaultColour;
}
// ---------------------------------------------------------------------------
/**
* Returns the matching colour for the specified value if a corresponding colour mapping is
* available for this scale. If no such mapping is found, then this scales' default colour is
* returned.
*
* @param value
* specifies the value to get matching colour for.
* @return the colour defined by this scale for the specified value.
*/
// ---------------------------------------------------------------------------
@SuppressWarnings("unchecked")
public Color getColourFor(Object value) {
for (ColourMapping<T> mapping : mappings) {
if (mapping.getMappedClass().isAssignableFrom(value.getClass())) {
if (mapping.inRange((T) value)) {
return mapping.getColour();
}
}
}
return this.defaultColour;
}
// ---------------------------------------------------------------------------
// ***************************************************************************
// * End of Class *
// ***************************************************************************
// ---------------------------------------------------------------------------
}
/**
* Copyright Luxembourg Institute of Science and Technology, 2018. All rights reserved. If you wish
* to use this code for any purpose, please contact the author(s).
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR
* IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND
* FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
* CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
* DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
* WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY
* WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
package lu.list.itis.dkd.tui.utility.scales;
import lu.list.itis.dkd.tui.exception.BuildException;
import lu.list.itis.dkd.tui.utility.CpsNamespace;
import org.jdom2.Document;
import org.jdom2.Element;
import org.jdom2.JDOMException;
import org.jdom2.input.SAXBuilder;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.io.File;
import java.io.IOException;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
/**
* @author Nico Mack [nico.mack@list.lu]
* @since 1.0
* @version 1.0.0
* @param <T>
*/
// ***************************************************************************
// * Class Definition and Members *
// ***************************************************************************
public class ScaleSystemBuilder {
private Document scenarioDocument;
private Map<String, ColourScale<?>> scales = new HashMap<>();
// ***************************************************************************
// * Constants *
// ***************************************************************************
private static final Logger LOGGER = LoggerFactory.getLogger(ScaleSystemBuilder.class.getSimpleName());
// ---------------------------------------------------------------------------
// ***************************************************************************
// * Constructor(s)
// ***************************************************************************
// ---------------------------------------------------------------------------
public ScaleSystemBuilder(File scenario) throws ScaleSystemException {
SAXBuilder builder = new SAXBuilder();
try {
scenarioDocument = builder.build(scenario);
} catch (IOException | JDOMException e) {
LOGGER.error("Error occured while trying to process the object bootstrapping file!", e); //$NON-NLS-1$
throw new ScaleSystemException("The document containing the bootstrapping information could not be processed!"); //$NON-NLS-1$
}
Element rootNode = this.scenarioDocument.getRootElement();
try {
this.buildFromElement(rootNode.getChild(CpsNamespace.SCALES_NODE));
} catch (BuildException exception) {
LOGGER.error("Error occured while building scale system!", exception); //$NON-NLS-1$
throw new ScaleSystemException("Failed to build scale system!"); //$NON-NLS-1$
}
}
// ---------------------------------------------------------------------------
// ***************************************************************************
// * Primitives
// ***************************************************************************
// ---------------------------------------------------------------------------
private void buildFromElement(Element scalesNode) throws BuildException {
if ((scalesNode != null) && scalesNode.getName().equalsIgnoreCase(CpsNamespace.SCALES_NODE)) {
List<Element> colourScales = scalesNode.getChildren(CpsNamespace.COLOUR_SCALE_NODE);
if ((colourScales != null) && !colourScales.isEmpty()) {
for (Element scaleElement : colourScales) {
ColourScale<?> scale = new ColourScale<>(scaleElement);
this.scales.put(scale.getName(), scale);
}
}
}
}
// ---------------------------------------------------------------------------
// ***************************************************************************
// * Class Body
// ***************************************************************************
// ---------------------------------------------------------------------------
public ColourScale<?> getScale(String scaleName) {
return this.scales.get(scaleName);
}
}
/**
* Copyright Luxembourg Institute of Science and Technology, 2016.
*
* This file is part of TULIP.
*
* TULIP is licensed under a dual-licensing scheme. For non-commercial purposes, the LGPL version 3,
* as stated below, is applicable. For all commercial purposes TULIP is licensed under a LIST
* proprietary license. Please contact LIST at tto@list.lu to obtain a commercial license.
*
* For all non-commercial purposes, TULIP is free software; you can redistribute it and/or modify it
* under the terms of the GNU Lesser General Public License as published by the Free Software
* Foundation, version 3 of the License.
*
* TULIP is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even
* the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser
* General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License along with TULIP. If
* not, see <http://www.gnu.org/licenses/lgpl-3.0.html>.
*/
package lu.list.itis.dkd.tui.utility.scales;
/**
*
* @author Nico Mack [nico.mack@list.lu]
* @since 1.1
* @version 1.3.0
*/
public class ScaleSystemException extends Exception {
/**
*
*/
private static final long serialVersionUID = 7422560120940501724L;
/**
*
* @param reason
*/
public ScaleSystemException(String reason) {
super(reason);
}
}
\ No newline at end of file
......@@ -126,7 +126,7 @@ public abstract class BaseCoxcombSliceBuilder<B extends BaseCoxcombSliceBuilder<
label = BootstrappingUtils.getContentAsString(rootElement, CpsNamespace.LABEL_NODE, BootstrappingUtils.OPTIONAL, Externalization.EMPTY_STRING, context);
radialLayout = BootstrappingUtils.getContentAsBoolean(rootElement, CpsNamespace.RADIAL_LAYOUT_NODE, BootstrappingUtils.OPTIONAL, Boolean.FALSE, context);
radialLayout = BootstrappingUtils.getContentAsBoolean(rootElement, Externalization.RADIAL_LAYOUT_NODE, BootstrappingUtils.OPTIONAL, Boolean.FALSE, context);
cappedDisplay = BootstrappingUtils.getContentAsBoolean(rootElement, CpsNamespace.CAPPED_DISPLAY_NODE, BootstrappingUtils.OPTIONAL, Boolean.FALSE, context);
blinkOnOutOfRange = BootstrappingUtils.getContentAsBoolean(rootElement, CpsNamespace.BLINK_ON_OUT_OF_RANGE_NODE, BootstrappingUtils.OPTIONAL, Boolean.FALSE, context);
faceIsTouchable = BootstrappingUtils.getContentAsBoolean(rootElement, CpsNamespace.FACE_IS_TOUCHABLE_NODE, BootstrappingUtils.OPTIONAL, Boolean.FALSE, context);
......
/**
* Copyright Luxembourg Institute of Science and Technology, 2018. All rights reserved. If you wish
* to use this code for any purpose, please contact the author(s).
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR
* IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND
* FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
* CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
* DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
* WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY
* WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
package lu.list.itis.dkd.tui.utility;
import static org.junit.Assert.assertTrue;
import org.junit.After;
import org.junit.Before;
import org.junit.Test;
/**
* @author mack
* @since [major].[minor]
* @version [major].[minor].[micro]
*/
public class ValueRangeTest {
/**
* @throws java.lang.Exception
*/
@Before
public void setUp() throws Exception {}
/**
* @throws java.lang.Exception
*/
@After
public void tearDown() throws Exception {}
@Test
public void testCompareTo() {
ValueRange<Double> range = new ValueRange<>(0.25d, 0.75d);
assertTrue(range.compareTo(0.15) < 0);
assertTrue(range.compareTo(0.25) == 0);
assertTrue(range.compareTo(0.56) == 0);
assertTrue(range.compareTo(0.75) == 0);
assertTrue(range.compareTo(0.76) > 0);
}
}
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment