You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@jackrabbit.apache.org by ju...@apache.org on 2007/01/08 23:09:02 UTC
svn commit: r494219 - in /jackrabbit/trunk/jackrabbit-jcr-commons/src:
main/java/org/apache/jackrabbit/name/ test/java/org/apache/jackrabbit/name/
Author: jukka
Date: Mon Jan 8 14:09:02 2007
New Revision: 494219
URL: http://svn.apache.org/viewvc?view=rev&rev=494219
Log:
JCR-688: Added the NameResolver interface, default implementation classes, and some tests.
Added:
jackrabbit/trunk/jackrabbit-jcr-commons/src/main/java/org/apache/jackrabbit/name/CachingNameResolver.java (with props)
jackrabbit/trunk/jackrabbit-jcr-commons/src/main/java/org/apache/jackrabbit/name/NameResolver.java (with props)
jackrabbit/trunk/jackrabbit-jcr-commons/src/main/java/org/apache/jackrabbit/name/ParsingNameResolver.java (with props)
jackrabbit/trunk/jackrabbit-jcr-commons/src/test/java/org/apache/jackrabbit/name/DummyNamespaceResolver.java (with props)
jackrabbit/trunk/jackrabbit-jcr-commons/src/test/java/org/apache/jackrabbit/name/ParsingNameResolverTest.java (with props)
Added: jackrabbit/trunk/jackrabbit-jcr-commons/src/main/java/org/apache/jackrabbit/name/CachingNameResolver.java
URL: http://svn.apache.org/viewvc/jackrabbit/trunk/jackrabbit-jcr-commons/src/main/java/org/apache/jackrabbit/name/CachingNameResolver.java?view=auto&rev=494219
==============================================================================
--- jackrabbit/trunk/jackrabbit-jcr-commons/src/main/java/org/apache/jackrabbit/name/CachingNameResolver.java (added)
+++ jackrabbit/trunk/jackrabbit-jcr-commons/src/main/java/org/apache/jackrabbit/name/CachingNameResolver.java Mon Jan 8 14:09:02 2007
@@ -0,0 +1,212 @@
+/*
+ * 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.
+ */
+package org.apache.jackrabbit.name;
+
+import java.util.HashMap;
+import java.util.Iterator;
+import java.util.Map;
+
+import javax.jcr.NamespaceException;
+
+/**
+ * Name resolver decorator that uses a generational cache to speed up
+ * parsing and formatting of JCR names. Uncached names are resolved using
+ * the underlying decorated name resolver.
+ * <p>
+ * The cache consists of three parts: a long term cache and two generations
+ * of recent cache entries. The two generations are used to collect recent new
+ * entries, and those entries that are used within two successive generations
+ * get promoted to the long term cache. The entries within the long term cache
+ * are discarded only when the size of the cache exceeds the given maximum
+ * cache size.
+ */
+public class CachingNameResolver implements NameResolver {
+
+ /**
+ * Default maximum cache size.
+ */
+ private static final int DEFAULT_CACHE_SIZE = 1000;
+
+ /**
+ * Divisor used to determine the default generation age from the
+ * maximum cache size.
+ */
+ private static final int DEFAULT_SIZE_AGE_RATIO = 10;
+
+ /**
+ * Decorated name resolver.
+ */
+ private final NameResolver resolver;
+
+ /**
+ * Maximum size of the name cache.
+ */
+ private final int maxSize;
+
+ /**
+ * Maximum age of a cache generation.
+ */
+ private final int maxAge;
+
+ /**
+ * Long term name cache. Read only.
+ */
+ private Map cache = new HashMap();
+
+ /**
+ * Old cache generation. Read only.
+ */
+ private Map old = new HashMap();
+
+ /**
+ * Young cache generation. Write only.
+ */
+ private Map young = new HashMap();
+
+ /**
+ * Age of the young cache generation.
+ */
+ private int age = 0;
+
+ /**
+ * Creates a caching name resolver.
+ *
+ * @param resolver decorated name resolver
+ * @param maxSize maximum size of the long term cache
+ * @param maxAge maximum age of a cache generation
+ */
+ public CachingNameResolver(NameResolver resolver, int maxSize, int maxAge) {
+ this.resolver = resolver;
+ this.maxSize = maxSize;
+ this.maxAge = maxAge;
+ }
+
+ /**
+ * Creates a caching name resolver using the default generation age for
+ * the given cache size.
+ *
+ * @param resolver decorated name resolver
+ * @param maxSize maximum size of the long term cache
+ */
+ public CachingNameResolver(NameResolver resolver, int maxSize) {
+ this(resolver, maxSize, maxSize / DEFAULT_SIZE_AGE_RATIO);
+ }
+
+ /**
+ * Creates a caching name resolver using the default size and
+ * generation age.
+ *
+ * @param resolver decorated name resolver
+ */
+ public CachingNameResolver(NameResolver resolver) {
+ this(resolver, DEFAULT_CACHE_SIZE);
+ }
+
+ /**
+ * Caches the given key-value pair and increases the age of the current
+ * cache generation. When the maximum age of a generation is reached,
+ * the following steps are taken:
+ * <ol>
+ * <li>The union of the two cache generations is calculated</li>
+ * <li>The union is added to the long term name cache</li>
+ * <li>If the cache size exceeds the maximum, only the union is kept</li>
+ * <li>A new cache generation is started</li>
+ * </ol>
+ *
+ * @param key key of the cache entry
+ * @param value value of the cache entry
+ */
+ private synchronized void cache(Object key, Object value) {
+ young.put(key, value);
+
+ if (++age == maxAge) {
+ Map union = new HashMap();
+ Iterator iterator = old.entrySet().iterator();
+ while (iterator.hasNext()) {
+ Map.Entry entry = (Map.Entry) iterator.next();
+ if (young.containsKey(entry.getKey())) {
+ union.put(entry.getKey(), entry.getValue());
+ }
+ }
+
+ if (!union.isEmpty()) {
+ if (cache.size() + union.size() <= maxSize) {
+ union.putAll(cache);
+ }
+ cache = union;
+ }
+
+ old = young;
+ young = new HashMap();
+ age = 0;
+ }
+ }
+
+ //--------------------------------------------------------< NameResolver >
+
+ /**
+ * Returns the qualified name for the given prefixed JCR name. The name
+ * is first looked up form the generational cache and the call gets
+ * delegated to the decorated name resolver only if the cache misses.
+ *
+ * @param name prefixed JCR name
+ * @return qualified name
+ * @throws NameException if the JCR name format is invalid
+ * @throws NamespaceException if the namespace prefix can not be resolved
+ */
+ public QName getQName(String name)
+ throws NameException, NamespaceException {
+ QName qname = (QName) cache.get(name);
+ if (qname == null) {
+ qname = (QName) old.get(name);
+ if (qname == null) {
+ qname = resolver.getQName(name);
+ }
+ cache(name, qname);
+ }
+ return qname;
+ }
+
+
+ /**
+ * Returns the prefixed JCR name for the given qualified name.
+ * If the name is in the default namespace, then the local name
+ * is returned without a prefix. Otherwise the name is first looked
+ * up form the generational cache and the call gets delegated to the
+ * decorated name resolver only if the cache misses.
+ *
+ * @param qname qualified name
+ * @return prefixed JCR name
+ * @throws NamespaceException if the namespace URI can not be resolved
+ */
+ public String getJCRName(QName qname) throws NamespaceException {
+ if (qname.getNamespaceURI().length() == 0) {
+ return qname.getLocalName();
+ }
+
+ String name = (String) cache.get(qname);
+ if (name == null) {
+ name = (String) old.get(qname);
+ if (name == null) {
+ name = resolver.getJCRName(qname);
+ }
+ cache(qname, name);
+ }
+ return name;
+ }
+
+}
Propchange: jackrabbit/trunk/jackrabbit-jcr-commons/src/main/java/org/apache/jackrabbit/name/CachingNameResolver.java
------------------------------------------------------------------------------
svn:eol-style = native
Added: jackrabbit/trunk/jackrabbit-jcr-commons/src/main/java/org/apache/jackrabbit/name/NameResolver.java
URL: http://svn.apache.org/viewvc/jackrabbit/trunk/jackrabbit-jcr-commons/src/main/java/org/apache/jackrabbit/name/NameResolver.java?view=auto&rev=494219
==============================================================================
--- jackrabbit/trunk/jackrabbit-jcr-commons/src/main/java/org/apache/jackrabbit/name/NameResolver.java (added)
+++ jackrabbit/trunk/jackrabbit-jcr-commons/src/main/java/org/apache/jackrabbit/name/NameResolver.java Mon Jan 8 14:09:02 2007
@@ -0,0 +1,46 @@
+/*
+ * 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.
+ */
+package org.apache.jackrabbit.name;
+
+import javax.jcr.NamespaceException;
+
+/**
+ * Resolver for prefixed JCR names and namespace-qualified
+ * {@link QName QNames}.
+ */
+public interface NameResolver {
+
+ /**
+ * Returns the qualified name for the given prefixed JCR name.
+ *
+ * @param name prefixed JCR name
+ * @return qualified name
+ * @throws NameException if the JCR name format is invalid
+ * @throws NamespaceException if the namespace prefix can not be resolved
+ */
+ QName getQName(String name) throws NameException, NamespaceException;
+
+ /**
+ * Returns the prefixed JCR name for the given qualified name.
+ *
+ * @param name qualified name
+ * @return prefixed JCR name
+ * @throws NamespaceException if the namespace URI can not be resolved
+ */
+ String getJCRName(QName name) throws NamespaceException;
+
+}
Propchange: jackrabbit/trunk/jackrabbit-jcr-commons/src/main/java/org/apache/jackrabbit/name/NameResolver.java
------------------------------------------------------------------------------
svn:eol-style = native
Added: jackrabbit/trunk/jackrabbit-jcr-commons/src/main/java/org/apache/jackrabbit/name/ParsingNameResolver.java
URL: http://svn.apache.org/viewvc/jackrabbit/trunk/jackrabbit-jcr-commons/src/main/java/org/apache/jackrabbit/name/ParsingNameResolver.java?view=auto&rev=494219
==============================================================================
--- jackrabbit/trunk/jackrabbit-jcr-commons/src/main/java/org/apache/jackrabbit/name/ParsingNameResolver.java (added)
+++ jackrabbit/trunk/jackrabbit-jcr-commons/src/main/java/org/apache/jackrabbit/name/ParsingNameResolver.java Mon Jan 8 14:09:02 2007
@@ -0,0 +1,154 @@
+/*
+ * 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.
+ */
+package org.apache.jackrabbit.name;
+
+import javax.jcr.NamespaceException;
+import javax.jcr.Session;
+
+/**
+ * Name resolver that parsers and formats prefixed JCR names.
+ * A {@link NamespaceResolver} is used for resolving the namespace prefixes.
+ */
+public class ParsingNameResolver implements NameResolver {
+
+ /**
+ * Set of invalid or potentially invalid name characters.
+ * Organized as a boolean array indexed by the ASCII character
+ * code for maximum lookup performance.
+ */
+ private static final boolean[] MAYBE_INVALID = new boolean[128];
+
+ /**
+ * Initializes the set of invalid or potentially invalid name characters.
+ */
+ static {
+ for (char ch = 0; ch < MAYBE_INVALID.length; ch++) {
+ MAYBE_INVALID[ch] = Character.isSpaceChar(ch);
+ }
+ MAYBE_INVALID['.'] = true;
+ MAYBE_INVALID[':'] = true;
+ MAYBE_INVALID['/'] = true;
+ MAYBE_INVALID['['] = true;
+ MAYBE_INVALID[']'] = true;
+ MAYBE_INVALID['*'] = true;
+ MAYBE_INVALID['|'] = true;
+ MAYBE_INVALID['"'] = true;
+ MAYBE_INVALID['\''] = true;
+ }
+
+ /**
+ * Checks whether the given character is an invalid or potentially an
+ * invalid name character.
+ *
+ * @param ch character
+ * @return <code>true</code> if the character maybe is invalid in a name,
+ * <code>false</code> if it is valid in any place within a name
+ */
+ private static boolean maybeInvalid(char ch) {
+ if (ch < MAYBE_INVALID.length) {
+ return MAYBE_INVALID[ch];
+ } else {
+ return Character.isSpaceChar(ch);
+ }
+ }
+
+ /**
+ * Namespace resolver.
+ */
+ private final NamespaceResolver resolver;
+
+ /**
+ * Creates a parsing name resolver.
+ *
+ * @param resolver namespace resolver
+ */
+ public ParsingNameResolver(NamespaceResolver resolver) {
+ this.resolver = resolver;
+ }
+
+ /**
+ * Creates a parsing name resolver for a JCR session.
+ *
+ * @param session JCR session
+ */
+ public ParsingNameResolver(Session session) {
+ this(new SessionNamespaceResolver(session));
+ }
+
+ //--------------------------------------------------------< NameResolver >
+
+ /**
+ * Parses the prefixed JCR name and returns the resolved qualified name.
+ *
+ * @param name prefixed JCR name
+ * @return qualified name
+ * @throws NameException if the JCR name format is invalid
+ * @throws NamespaceException if the namespace prefix can not be resolved
+ */
+ public QName getQName(String name)
+ throws NameException, NamespaceException {
+ int length = name.length();
+ int colon = -1;
+
+ for (int i = 0; i < length; i++) {
+ char ch = name.charAt(i);
+ if (maybeInvalid(ch)) {
+ if (ch == ':' && colon == -1 && 0 < i && i + 1 < length) {
+ colon = i; // Detect the position of the first colon
+ } else if (ch == ' ' && colon + 1 < i && i + 1 < length) {
+ // Allow spaces in the middle of the local name
+ } else if (ch == '.'
+ && (i > 0 || length > 2
+ || (length == 2 && name.charAt(1) != '.'))) {
+ // Allow dots when the name is not "." or ".."
+ } else {
+ throw new IllegalNameException("Invalid name: " + name);
+ }
+ }
+ }
+
+ if (length == 0) {
+ throw new IllegalNameException("Empty name");
+ } else if (colon == -1) {
+ return new QName(QName.NS_DEFAULT_URI, name);
+ } else {
+ // Resolve the prefix (and validate the prefix format!)
+ String uri = resolver.getURI(name.substring(0, colon));
+ return new QName(uri, name.substring(colon + 1));
+ }
+ }
+
+ /**
+ * Returns the prefixed JCR name for the given qualified name.
+ * If the name is in the default namespace, then the local name
+ * is returned without a prefix. Otherwise the prefix for the
+ * namespace is resolved and used to construct returned the JCR name.
+ *
+ * @param name qualified name
+ * @return prefixed JCR name
+ * @throws NamespaceException if the namespace URI can not be resolved
+ */
+ public String getJCRName(QName name) throws NamespaceException {
+ String uri = name.getNamespaceURI();
+ if (uri.length() == 0) {
+ return name.getLocalName();
+ } else {
+ return resolver.getPrefix(uri) + ":" + name.getLocalName();
+ }
+ }
+
+}
Propchange: jackrabbit/trunk/jackrabbit-jcr-commons/src/main/java/org/apache/jackrabbit/name/ParsingNameResolver.java
------------------------------------------------------------------------------
svn:eol-style = native
Added: jackrabbit/trunk/jackrabbit-jcr-commons/src/test/java/org/apache/jackrabbit/name/DummyNamespaceResolver.java
URL: http://svn.apache.org/viewvc/jackrabbit/trunk/jackrabbit-jcr-commons/src/test/java/org/apache/jackrabbit/name/DummyNamespaceResolver.java?view=auto&rev=494219
==============================================================================
--- jackrabbit/trunk/jackrabbit-jcr-commons/src/test/java/org/apache/jackrabbit/name/DummyNamespaceResolver.java (added)
+++ jackrabbit/trunk/jackrabbit-jcr-commons/src/test/java/org/apache/jackrabbit/name/DummyNamespaceResolver.java Mon Jan 8 14:09:02 2007
@@ -0,0 +1,78 @@
+/*
+ * 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.
+ */
+package org.apache.jackrabbit.name;
+
+import javax.jcr.NamespaceException;
+
+import org.apache.jackrabbit.util.XMLChar;
+
+/**
+ * Dummy namespace resolver for use in unit testing. This namespace resolver
+ * maps each valid XML prefix string to the same string as the namespace URI
+ * and vice versa.
+ */
+class DummyNamespaceResolver implements NamespaceResolver {
+
+ /**
+ * Returns the given prefix as the corresponding namespace URI after
+ * validating the correct format of the prefix.
+ *
+ * @param prefix namespace prefix
+ * @return the given prefix
+ * @throws NamespaceException if the given prefix is not a valid XML prefix
+ */
+ public String getURI(String prefix) throws NamespaceException {
+ if (XMLChar.isValidNCName(prefix)) {
+ return prefix;
+ } else {
+ throw new NamespaceException("Invalid prefix: " + prefix);
+ }
+ }
+
+ /**
+ * Returns the given namespace URI as the corresponding prefix.
+ *
+ * @param uri namespace URI
+ * @return the given URI
+ */
+ public String getPrefix(String uri) {
+ return uri;
+ }
+
+ /**
+ * Not implemented.
+ *
+ * @param qName ignored
+ * @return nothing
+ * @throws UnsupportedOperationException always thrown
+ */
+ public String getJCRName(QName qName) throws UnsupportedOperationException {
+ throw new UnsupportedOperationException();
+ }
+
+ /**
+ * Not implemented.
+ *
+ * @param jcrName ignored
+ * @return nothing
+ * @throws UnsupportedOperationException always thrown
+ */
+ public QName getQName(String jcrName) throws UnsupportedOperationException {
+ throw new UnsupportedOperationException();
+ }
+
+}
Propchange: jackrabbit/trunk/jackrabbit-jcr-commons/src/test/java/org/apache/jackrabbit/name/DummyNamespaceResolver.java
------------------------------------------------------------------------------
svn:eol-style = native
Added: jackrabbit/trunk/jackrabbit-jcr-commons/src/test/java/org/apache/jackrabbit/name/ParsingNameResolverTest.java
URL: http://svn.apache.org/viewvc/jackrabbit/trunk/jackrabbit-jcr-commons/src/test/java/org/apache/jackrabbit/name/ParsingNameResolverTest.java?view=auto&rev=494219
==============================================================================
--- jackrabbit/trunk/jackrabbit-jcr-commons/src/test/java/org/apache/jackrabbit/name/ParsingNameResolverTest.java (added)
+++ jackrabbit/trunk/jackrabbit-jcr-commons/src/test/java/org/apache/jackrabbit/name/ParsingNameResolverTest.java Mon Jan 8 14:09:02 2007
@@ -0,0 +1,106 @@
+/*
+ * 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.
+ */
+package org.apache.jackrabbit.name;
+
+import javax.jcr.NamespaceException;
+
+
+import junit.framework.TestCase;
+
+/**
+ * Test cases for the {@link ParsingNameResolver} class.
+ */
+public class ParsingNameResolverTest extends TestCase {
+
+ /**
+ * Name resolver being tested.
+ */
+ private NameResolver resolver =
+ new ParsingNameResolver(new DummyNamespaceResolver());
+
+ /**
+ * Checks that the given name resolves to the given namespace URI and
+ * local part.
+ *
+ * @param name JCR name
+ * @param uri namespace URI
+ * @param local local part
+ */
+ private void assertValidName(String name, String uri, String local) {
+ try {
+ QName qname = resolver.getQName(name);
+ assertEquals(name, uri, qname.getNamespaceURI());
+ assertEquals(name, local, qname.getLocalName());
+ assertEquals(name, name, resolver.getJCRName(qname));
+ } catch (NameException e) {
+ fail(name);
+ } catch (NamespaceException e) {
+ fail(name);
+ }
+ }
+
+ /**
+ * Tests that valid names are properly resolved.
+ */
+ public void testValidNames() {
+ assertValidName("x", "", "x");
+ assertValidName("name", "", "name");
+ assertValidName("space name", "", "space name");
+ assertValidName("x:y", "x", "y");
+ assertValidName("prefix:name", "prefix", "name");
+ assertValidName("prefix:space name", "prefix", "space name");
+ }
+
+ /**
+ * Checks that the given name fails to resolve.
+ *
+ * @param name JCR name
+ */
+ private void assertInvalidName(String name) {
+ try {
+ resolver.getQName(name);
+ fail(name);
+ } catch (NameException e) {
+ } catch (NamespaceException e) {
+ }
+ }
+
+ /**
+ * Tests that resolution of invalid names fails.
+ */
+ public void testInvalidNames() {
+ assertInvalidName("");
+ assertInvalidName(":name");
+ assertInvalidName(".");
+ assertInvalidName("..");
+ assertInvalidName("pre:");
+ assertInvalidName(" name");
+ assertInvalidName(" prefix: name");
+ assertInvalidName("prefix: name");
+ assertInvalidName("prefix:name ");
+ assertInvalidName("pre fix:name");
+ assertInvalidName("prefix :name");
+ assertInvalidName("name/name");
+ assertInvalidName("name[]");
+ assertInvalidName("name[1]");
+ assertInvalidName("name[name");
+ assertInvalidName("name]name");
+ assertInvalidName("name*name");
+ assertInvalidName("prefix:name:name");
+ }
+
+}
Propchange: jackrabbit/trunk/jackrabbit-jcr-commons/src/test/java/org/apache/jackrabbit/name/ParsingNameResolverTest.java
------------------------------------------------------------------------------
svn:eol-style = native