/* * The MIT License * * Copyright 2013 Tim Boudreau. * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal * in the Software without restriction, including without limitation the rights * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell * copies of the Software, and to permit persons to whom the Software is * furnished to do so, subject to the following conditions: * * The above copyright notice and this permission notice shall be included in * all copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN * THE SOFTWARE. */ package com.mastfrog.url; import com.mastfrog.util.Checks; import org.openide.util.NbBundle; /** * One Label in a <a href="Host.html"><code>Host</code></a> - i.e. one component * of an IP address or host name such as <code>www.<b>example</b>.com</code> or * <code>192.<b>168</b>.2.32</code>. * * @author Tim Boudreau */ public final class Label implements URLComponent { private static final long serialVersionUID = 2L; private final String label; public Label(String domainPart) { Checks.notNull("domainPart", domainPart); this.label = domainPart; } public boolean isEmpty() { return label.isEmpty(); } public int length() { return label.length(); } @Override public String toString() { return label.toLowerCase(); } public boolean isNumeric() { int len = label.length(); boolean result = len > 0; if (result) { for (int i = 0; i < len && result; i++) { result &= URLBuilder.isNumber(label.charAt(i)); } } return result; } /** * Get the literal label. * @return The label */ public String getLabel() { return label; } public boolean isHex() { boolean result = true; // empty is considered hex 0 int len = label.length(); for (int i = 0; i < len && result; i++) { result &= isHexNumber(label.charAt(i)); } return result; } private boolean isHexNumber(char c) { return (c >= '0' && c <= '9') || (c >= 'A' && c <= 'F') || (c >= 'a' && c <= 'f'); } /** * Get an int value for this label, if it can be parsed, or -1 if not. * @param hex Whether to parse as hexadecimal * @return An int value */ public int asInt(boolean hex) { try { if (hex) { if (label.isEmpty()) { return 0; } return Integer.parseInt(label, 16); } else { return Integer.parseInt(label); } } catch (NumberFormatException e) { return -1; } } /** * Get an int value for this label. * * @return An integer value or -1 if the label value is not a number * @deprecated Use asInt(hex) instead */ @Deprecated public int asInt() { return asInt(isHex()); } public boolean isValidIpComponent() { return isValidIpV4Component() || isValidIpV6Component(); } public boolean isValidIpV6Component() { if (label.isEmpty()) { return true; } if (label.length() <= 4) { boolean result = true; for (char c : label.toCharArray()) { result = isHexNumber(c); if (!result) { break; } } return result; } return false; } public boolean isValidIpV4Component() { boolean result = isNumeric(); if (result) { int comp = asInt(false); return comp >= 0 && comp <= 255; } return false; } @Override public boolean isValid() { if (label.length() == 0) { return true; } if (label.length() > 63) { return false; } boolean ipV6 = isValidIpV6Component(); int val = asInt(ipV6); if (val != -1) { int limit = ipV6 ? 65536 : 255; if (val < 0) { return false; } return val <= limit; } char[] chars = label.toCharArray(); for (int i = 0; i < chars.length; i++) { char c = chars[i]; switch (c) { case '-': if (i == 0 || i == chars.length - 1) { return false; } break; case '.': return false; } boolean number = URLBuilder.isNumber(c); boolean letter = URLBuilder.isLetter(c); if (!number && !letter) { switch (c) { case '-': return true; } return false; } } return true; } @Override public String getComponentName() { return NbBundle.getMessage(Label.class, "label"); } @Override public void appendTo(StringBuilder sb) { sb.append(toString()); } @Override public boolean equals(Object obj) { if (obj == null) { return false; } if (getClass() != obj.getClass()) { return false; } final Label other = (Label) obj; if (this.label.equals(other.label)) { return true; } if ((this.isValidIpV4Component() && other.isValidIpV4Component()) || (this.isValidIpV6Component() && other.isValidIpV6Component())) { return asInt(true) == other.asInt(true); } //lower case normalization return toString().equals(other.toString()); } private int cachedHashCode = -1; @Override public int hashCode() { if (cachedHashCode != -1) { return cachedHashCode; } if (isNumeric() || isHex()) { return cachedHashCode = asInt(true); } int hash = 3; hash = 59 * hash + (this.label != null ? this.toString().hashCode() : 0); return cachedHashCode = hash; } }