diff --git a/httpclient5/src/main/java/org/apache/hc/client5/http/validator/ETag.java b/httpclient5/src/main/java/org/apache/hc/client5/http/validator/ETag.java new file mode 100644 index 000000000..a5b32149c --- /dev/null +++ b/httpclient5/src/main/java/org/apache/hc/client5/http/validator/ETag.java @@ -0,0 +1,191 @@ +/* + * ==================================================================== + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + * ==================================================================== + * + * This software consists of voluntary contributions made by many + * individuals on behalf of the Apache Software Foundation. For more + * information on the Apache Software Foundation, please see + * . + * + */ + +package org.apache.hc.client5.http.validator; + +import org.apache.hc.core5.annotation.Contract; +import org.apache.hc.core5.annotation.Internal; +import org.apache.hc.core5.annotation.ThreadingBehavior; +import org.apache.hc.core5.http.Header; +import org.apache.hc.core5.http.MessageHeaders; +import org.apache.hc.core5.util.Args; +import org.apache.hc.core5.util.CharArrayBuffer; +import org.apache.hc.core5.util.LangUtils; +import org.apache.hc.core5.util.Tokenizer; + +/** + * Represents ETag value. + * + * @since 5.4 + */ +@Contract(threading = ThreadingBehavior.IMMUTABLE) +public final class ETag { + + private final String value; + private final ValidatorType type; + + public ETag(final String value, final ValidatorType type) { + this.value = Args.notNull(value, "Value"); + this.type = Args.notNull(type, "Validator type"); + } + + public ETag(final String value) { + this(value, ValidatorType.STRONG); + } + + public ValidatorType getType() { + return type; + } + + public String getValue() { + return value; + } + + public static boolean strongCompare(final ETag t1, final ETag t2) { + if (t1 == null || t2 == null) { + return false; + } + return t1.type == ValidatorType.STRONG && t1.type == t2.type && t1.value.equals(t2.value); + } + + public static boolean weakCompare(final ETag t1, final ETag t2) { + if (t1 == null || t2 == null) { + return false; + } + return t1.value.equals(t2.value); + } + + static ETag parse(final CharSequence buf, final Tokenizer.Cursor cursor) { + final Tokenizer tokenizer = Tokenizer.INSTANCE; + tokenizer.skipWhiteSpace(buf, cursor); + + ValidatorType type = ValidatorType.STRONG; + + if (!cursor.atEnd() && buf.charAt(cursor.getPos()) == 'W') { + cursor.updatePos(cursor.getPos() + 1); + if (!cursor.atEnd() && buf.charAt(cursor.getPos()) == '/') { + type = ValidatorType.WEAK; + cursor.updatePos(cursor.getPos() + 1); + } else { + return null; + } + } + + String value = null; + if (!cursor.atEnd() && buf.charAt(cursor.getPos()) == '"') { + cursor.updatePos(cursor.getPos() + 1); + final StringBuilder sb = new StringBuilder(); + for (;;) { + if (cursor.atEnd()) { + return null; + } + final char ch = buf.charAt(cursor.getPos()); + cursor.updatePos(cursor.getPos() + 1); + if (ch == '"') { + break; + } else { + sb.append(ch); + } + } + value = sb.toString(); + } + + tokenizer.skipWhiteSpace(buf, cursor); + if (!cursor.atEnd()) { + return null; + } + + return new ETag(value, type); + } + + public static ETag parse(final String s) { + if (s == null) { + return null; + } + final Tokenizer.Cursor cursor = new Tokenizer.Cursor(0, s.length()); + return parse(s, cursor); + } + + public static ETag parse(final Header h) { + if (h == null) { + return null; + } + final String value = h.getValue(); + if (value == null) { + return null; + } + return parse(value); + } + + public static ETag get(final MessageHeaders message) { + if (message == null) { + return null; + } + return parse(message.getFirstHeader(HttpHeaders.ETAG)); + } + + @Internal + public void format(final CharArrayBuffer buf) { + if (buf == null) { + return; + } + if (type == ValidatorType.WEAK) { + buf.append("W/"); + } + buf.append('"'); + buf.append(value); + buf.append('"'); + } + + @Override + public boolean equals(final Object o) { + if (this == o) { + return true; + } + if (o instanceof ETag) { + final ETag that = (ETag) o; + return this.type == that.type && this.value.equals(that.value); + } + return false; + } + + @Override + public int hashCode() { + int hash = LangUtils.HASH_SEED; + hash = LangUtils.hashCode(hash, type); + hash = LangUtils.hashCode(hash, value); + return hash; + } + + @Override + public String toString() { + final CharArrayBuffer buf = new CharArrayBuffer(64); + format(buf); + return buf.toString(); + } + +} diff --git a/httpclient5/src/main/java/org/apache/hc/client5/http/validator/ValidatorType.java b/httpclient5/src/main/java/org/apache/hc/client5/http/validator/ValidatorType.java new file mode 100644 index 000000000..eb13960fc --- /dev/null +++ b/httpclient5/src/main/java/org/apache/hc/client5/http/validator/ValidatorType.java @@ -0,0 +1,37 @@ +/* + * ==================================================================== + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + * ==================================================================== + * + * This software consists of voluntary contributions made by many + * individuals on behalf of the Apache Software Foundation. For more + * information on the Apache Software Foundation, please see + * . + * + */ + +package org.apache.hc.client5.http.validator; + +/** + * @since 5.4 + */ +public enum ValidatorType { + + STRONG, WEAK + +} \ No newline at end of file diff --git a/httpclient5/src/test/java/org/apache/hc/client5/http/validator/TestETag.java b/httpclient5/src/test/java/org/apache/hc/client5/http/validator/TestETag.java new file mode 100644 index 000000000..865683094 --- /dev/null +++ b/httpclient5/src/test/java/org/apache/hc/client5/http/validator/TestETag.java @@ -0,0 +1,93 @@ +/* + * ==================================================================== + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + * ==================================================================== + * + * This software consists of voluntary contributions made by many + * individuals on behalf of the Apache Software Foundation. For more + * information on the Apache Software Foundation, please see + * . + * + */ +package org.apache.hc.client5.http.validator; + +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.Test; + +public class TestETag { + + @Test + public void testHashCodeEquals() { + final ETag tag1 = new ETag("this"); + final ETag tag2 = new ETag("this"); + final ETag tag3 = new ETag("this", ValidatorType.WEAK); + final ETag tag4 = new ETag("that", ValidatorType.WEAK); + + Assertions.assertEquals(tag1.hashCode(), tag2.hashCode()); + Assertions.assertNotEquals(tag2.hashCode(), tag3.hashCode()); + Assertions.assertNotEquals(tag3.hashCode(), tag4.hashCode()); + + Assertions.assertEquals(tag1, tag2); + Assertions.assertNotEquals(tag2, tag3); + Assertions.assertNotEquals(tag3, tag4); + } + + @Test + public void testToString() { + Assertions.assertEquals("\"blah\"", new ETag("blah").toString()); + Assertions.assertEquals("W/\"blah\"", new ETag("blah", ValidatorType.WEAK).toString()); + Assertions.assertEquals("\"\"", new ETag("").toString()); + } + + @Test + public void testParse() { + Assertions.assertEquals(new ETag("blah", ValidatorType.WEAK), ETag.parse(" W/\"blah\" ")); + Assertions.assertEquals(new ETag(" huh?"), ETag.parse(" \" huh?\" ")); + Assertions.assertEquals(new ETag(" ", ValidatorType.WEAK), ETag.parse("W/\" \"")); + Assertions.assertEquals(new ETag(""), ETag.parse("\"\"")); + Assertions.assertNull(ETag.parse("wrong")); + Assertions.assertNull(ETag.parse("w/\"wrong\"")); + Assertions.assertNull(ETag.parse("W /\"wrong\"")); + Assertions.assertNull(ETag.parse("W/ \"wrong\"")); + Assertions.assertNull(ETag.parse("W/wrong")); + Assertions.assertNull(ETag.parse("\"cut")); + Assertions.assertNull(ETag.parse(" \"")); + Assertions.assertNull(ETag.parse("W/\"")); + } + + @Test + public void testComparison() { + Assertions.assertFalse(ETag.strongCompare(new ETag("1", ValidatorType.WEAK), new ETag("1", ValidatorType.WEAK))); + Assertions.assertTrue(ETag.weakCompare(new ETag("1", ValidatorType.WEAK), new ETag("1", ValidatorType.WEAK))); + Assertions.assertFalse(ETag.strongCompare(new ETag("1", ValidatorType.WEAK), new ETag("2", ValidatorType.WEAK))); + Assertions.assertFalse(ETag.weakCompare(new ETag("1", ValidatorType.WEAK), new ETag("2", ValidatorType.WEAK))); + Assertions.assertFalse(ETag.strongCompare(new ETag("1", ValidatorType.WEAK), new ETag("1"))); + Assertions.assertTrue(ETag.weakCompare(new ETag("1", ValidatorType.WEAK), new ETag("1"))); + Assertions.assertTrue(ETag.strongCompare(new ETag("1"), new ETag("1"))); + Assertions.assertTrue(ETag.weakCompare(new ETag("1"), new ETag("1"))); + + Assertions.assertFalse(ETag.weakCompare(new ETag("1", ValidatorType.WEAK), null)); + Assertions.assertFalse(ETag.weakCompare(null, new ETag("1", ValidatorType.WEAK))); + Assertions.assertFalse(ETag.weakCompare(null, null)); + + Assertions.assertFalse(ETag.strongCompare(new ETag("1"), null)); + Assertions.assertFalse(ETag.strongCompare(null, new ETag("1"))); + Assertions.assertFalse(ETag.strongCompare(null, null)); + } + +}