You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@subversion.apache.org by hw...@apache.org on 2010/03/01 23:46:46 UTC

svn commit: r917772 - in /subversion/trunk/subversion/bindings/javahl: native/ src/org/apache/subversion/javahl/callback/ src/org/tigris/subversion/javahl/ tests/org/apache/subversion/javahl/

Author: hwright
Date: Mon Mar  1 22:46:45 2010
New Revision: 917772

URL: http://svn.apache.org/viewvc?rev=917772&view=rev
Log:
JavaHL: Return properties as byte[] throughout the callback interfaces.

We use byte[] in place of String because there could be binary data in the
property, and the conversion to String would truncate the property at any
NULL bytes.

[ in subversion/bindings/javahl/ ]
* tests/org/apache/subversion/javahl/BasicTests.java
  (testBasicProperties, getMergeinfoRevisions, testCommitRevprops,
   MyProplistCallback, collectProperties, collectLogMessages,
   BlameCallbackImpl):
    Update for byte[] property values.

* native/ProplistCallback.cpp
  (makeMapFromHash): Put the byte array into the Map.

* src/org/apache/subversion/javahl/callback/LogMessageCallback.java
  (LogMessageCallback): Change the interface to return byte[].

* src/org/apache/subversion/javahl/callback/ProplistCallback.java
  (singlePath): Same.

* src/org/apache/subversion/javahl/callback/BlameCallback.java
  (singleLine): Same.

* src/org/tigris/subversion/javahl/SVNClient.java
  (logMessages, properties, blame): Update wrappers for the byte[] switch.

* src/org/tigris/subversion/javahl/ProplistCallbackImpl.java
  (ProplistCallback): Wrap the byte[] for backward compat.

Modified:
    subversion/trunk/subversion/bindings/javahl/native/ProplistCallback.cpp
    subversion/trunk/subversion/bindings/javahl/src/org/apache/subversion/javahl/callback/BlameCallback.java
    subversion/trunk/subversion/bindings/javahl/src/org/apache/subversion/javahl/callback/LogMessageCallback.java
    subversion/trunk/subversion/bindings/javahl/src/org/apache/subversion/javahl/callback/ProplistCallback.java
    subversion/trunk/subversion/bindings/javahl/src/org/tigris/subversion/javahl/ProplistCallbackImpl.java
    subversion/trunk/subversion/bindings/javahl/src/org/tigris/subversion/javahl/SVNClient.java
    subversion/trunk/subversion/bindings/javahl/tests/org/apache/subversion/javahl/BasicTests.java

Modified: subversion/trunk/subversion/bindings/javahl/native/ProplistCallback.cpp
URL: http://svn.apache.org/viewvc/subversion/trunk/subversion/bindings/javahl/native/ProplistCallback.cpp?rev=917772&r1=917771&r2=917772&view=diff
==============================================================================
--- subversion/trunk/subversion/bindings/javahl/native/ProplistCallback.cpp (original)
+++ subversion/trunk/subversion/bindings/javahl/native/ProplistCallback.cpp Mon Mar  1 22:46:45 2010
@@ -158,7 +158,8 @@
       if (JNIUtil::isJavaExceptionThrown())
         return NULL;
 
-      jstring jpropVal = JNIUtil::makeJString(val->data);
+      jbyteArray jpropVal = JNIUtil::makeJByteArray(
+                                    (const signed char *)val->data, val->len);
       if (JNIUtil::isJavaExceptionThrown())
         return NULL;
 

Modified: subversion/trunk/subversion/bindings/javahl/src/org/apache/subversion/javahl/callback/BlameCallback.java
URL: http://svn.apache.org/viewvc/subversion/trunk/subversion/bindings/javahl/src/org/apache/subversion/javahl/callback/BlameCallback.java?rev=917772&r1=917771&r2=917772&view=diff
==============================================================================
--- subversion/trunk/subversion/bindings/javahl/src/org/apache/subversion/javahl/callback/BlameCallback.java (original)
+++ subversion/trunk/subversion/bindings/javahl/src/org/apache/subversion/javahl/callback/BlameCallback.java Mon Mar  1 22:46:45 2010
@@ -48,8 +48,8 @@
      * @param localChange       true if the line was locally modified.
      */
     public void singleLine(long lineNum, long revision,
-                           Map<String, String> revProps, long mergedRevision,
-                           Map<String, String> mergedRevProps,
+                           Map<String, byte[]> revProps, long mergedRevision,
+                           Map<String, byte[]> mergedRevProps,
                            String mergedPath, String line, boolean localChange)
         throws ClientException;
 }

Modified: subversion/trunk/subversion/bindings/javahl/src/org/apache/subversion/javahl/callback/LogMessageCallback.java
URL: http://svn.apache.org/viewvc/subversion/trunk/subversion/bindings/javahl/src/org/apache/subversion/javahl/callback/LogMessageCallback.java?rev=917772&r1=917771&r2=917772&view=diff
==============================================================================
--- subversion/trunk/subversion/bindings/javahl/src/org/apache/subversion/javahl/callback/LogMessageCallback.java (original)
+++ subversion/trunk/subversion/bindings/javahl/src/org/apache/subversion/javahl/callback/LogMessageCallback.java Mon Mar  1 22:46:45 2010
@@ -59,6 +59,6 @@
      */
     public void singleMessage(ChangePath[] changedPaths,
                               long revision,
-                              Map<String, String> revprops,
+                              Map<String, byte[]> revprops,
                               boolean hasChildren);
 }

Modified: subversion/trunk/subversion/bindings/javahl/src/org/apache/subversion/javahl/callback/ProplistCallback.java
URL: http://svn.apache.org/viewvc/subversion/trunk/subversion/bindings/javahl/src/org/apache/subversion/javahl/callback/ProplistCallback.java?rev=917772&r1=917771&r2=917772&view=diff
==============================================================================
--- subversion/trunk/subversion/bindings/javahl/src/org/apache/subversion/javahl/callback/ProplistCallback.java (original)
+++ subversion/trunk/subversion/bindings/javahl/src/org/apache/subversion/javahl/callback/ProplistCallback.java Mon Mar  1 22:46:45 2010
@@ -36,5 +36,5 @@
      * @param path        the path.
      * @param properties  the properties on the path.
      */
-    public void singlePath(String path, Map<String, String> properties);
+    public void singlePath(String path, Map<String, byte[]> properties);
 }

Modified: subversion/trunk/subversion/bindings/javahl/src/org/tigris/subversion/javahl/ProplistCallbackImpl.java
URL: http://svn.apache.org/viewvc/subversion/trunk/subversion/bindings/javahl/src/org/tigris/subversion/javahl/ProplistCallbackImpl.java?rev=917772&r1=917771&r2=917772&view=diff
==============================================================================
--- subversion/trunk/subversion/bindings/javahl/src/org/tigris/subversion/javahl/ProplistCallbackImpl.java (original)
+++ subversion/trunk/subversion/bindings/javahl/src/org/tigris/subversion/javahl/ProplistCallbackImpl.java Mon Mar  1 22:46:45 2010
@@ -33,16 +33,24 @@
  */
 public class ProplistCallbackImpl implements ProplistCallback
 {
-    Map<String, Map<String, String>> propMap =
-                                new HashMap<String, Map<String, String>>();
+    Map<String, Map<String, byte[]>> propMap =
+                                new HashMap<String, Map<String, byte[]>>();
 
-    public void singlePath(String path, Map<String, String> props)
+    public void singlePath(String path, Map<String, byte[]> props)
     {
         propMap.put(path, props);
     }
 
     public Map<String, String> getProperties(String path)
     {
-        return propMap.get(path);
+        Map<String, String> props = new HashMap<String, String>();
+        Map<String, byte[]> pdata = propMap.get(path);
+
+        for (String key : pdata.keySet())
+        {
+            props.put(key, new String(pdata.get(key)));
+        }
+
+        return props;
     }
 }

Modified: subversion/trunk/subversion/bindings/javahl/src/org/tigris/subversion/javahl/SVNClient.java
URL: http://svn.apache.org/viewvc/subversion/trunk/subversion/bindings/javahl/src/org/tigris/subversion/javahl/SVNClient.java?rev=917772&r1=917771&r2=917772&view=diff
==============================================================================
--- subversion/trunk/subversion/bindings/javahl/src/org/tigris/subversion/javahl/SVNClient.java (original)
+++ subversion/trunk/subversion/bindings/javahl/src/org/tigris/subversion/javahl/SVNClient.java Mon Mar  1 22:46:45 2010
@@ -28,6 +28,7 @@
 import java.io.OutputStream;
 
 import java.util.Map;
+import java.util.HashMap;
 import java.util.List;
 import java.util.HashSet;
 import java.util.Arrays;
@@ -504,8 +505,11 @@
 
             public void singleMessage(
                     org.apache.subversion.javahl.ChangePath[] aChangedPaths,
-                    long revision, Map revprops, boolean hasChildren)
+                    long revision, Map<String, byte[]> revprops,
+                    boolean hasChildren)
             {
+                Map<String, String> oldRevprops =
+                                                new HashMap<String, String>();
                 ChangePath[] changedPaths;
                 
                 if (aChangedPaths != null)
@@ -522,7 +526,12 @@
                     changedPaths = null;
                 }
 
-                callback.singleMessage(changedPaths, revision, revprops,
+                for (String key : revprops.keySet())
+                {
+                    oldRevprops.put(key, new String(revprops.get(key)));
+                }
+
+                callback.singleMessage(changedPaths, revision, oldRevprops,
                                        hasChildren);
             }
         }
@@ -1716,7 +1725,7 @@
         int i = 0;
         for (String key : propMap.keySet())
         {
-            props[i] = new PropertyData(path, key, (String) propMap.get(key));
+            props[i] = new PropertyData(path, key, propMap.get(key));
             i++;
         }
 
@@ -2182,15 +2191,16 @@
                 try
                 {
                     oldCallback.singleLine(
-                        df.parse((String) revProps.get("svn:date")),
+                        df.parse(new String((byte[]) revProps.get("svn:date"))),
                         revision,
-                        (String) revProps.get("svn:author"),
+                        new String((byte[]) revProps.get("svn:author")),
                         mergedRevProps == null ? null
-                            : df.parse((String)
-                                            mergedRevProps.get("svn:date")),
+                            : df.parse(new String((byte [])
+                                            mergedRevProps.get("svn:date"))),
                         mergedRevision,
                         mergedRevProps == null ? null
-                            : (String) mergedRevProps.get("svn:author"),
+                            : new String((byte[])
+                                mergedRevProps.get("svn:author")),
                         mergedPath, line);
                 }
                 catch (ParseException e)

Modified: subversion/trunk/subversion/bindings/javahl/tests/org/apache/subversion/javahl/BasicTests.java
URL: http://svn.apache.org/viewvc/subversion/trunk/subversion/bindings/javahl/tests/org/apache/subversion/javahl/BasicTests.java?rev=917772&r1=917771&r2=917772&view=diff
==============================================================================
--- subversion/trunk/subversion/bindings/javahl/tests/org/apache/subversion/javahl/BasicTests.java (original)
+++ subversion/trunk/subversion/bindings/javahl/tests/org/apache/subversion/javahl/BasicTests.java Mon Mar  1 22:46:45 2010
@@ -764,11 +764,11 @@
         MyProplistCallback callback = new MyProplistCallback();
 
         client.properties(itemPath, null, null, Depth.empty, null, callback);
-        Map<String, String> propMap = callback.getProperties(itemPath);
+        Map<String, byte[]> propMap = callback.getProperties(itemPath);
         for (String key : propMap.keySet())
         {
             assertEquals("cqcq", key);
-            assertEquals("qrz", (String) propMap.get(key));
+            assertEquals("qrz", new String(propMap.get(key)));
         }
 
         wc.setItemPropStatus("A/B/E/alpha", Status.Kind.modified);
@@ -2361,7 +2361,7 @@
             List<Long> revList = new ArrayList<Long>();
 
             public void singleMessage(ChangePath[] changedPaths, long revision,
-                    Map<String, String> revprops, boolean hasChildren) {
+                    Map<String, byte[]> revprops, boolean hasChildren) {
                 revList.add(new Long(revision));
             }
 
@@ -3457,17 +3457,17 @@
 
         class RevpropLogCallback implements LogMessageCallback
         {
-            Map<String, String> revprops;
+            Map<String, byte[]> revprops;
 
             public void singleMessage(ChangePath[] changedPaths,
                                       long revision,
-                                      Map<String, String> revprops,
+                                      Map<String, byte[]> revprops,
                                       boolean hasChildren)
             {
                 this.revprops = revprops;
             }
 
-            public Map<String, String> getRevprops()
+            public Map<String, byte[]> getRevprops()
             {
                 return revprops;
             }
@@ -3511,7 +3511,7 @@
                                            Revision.getInstance(2)),
                            false, false, false, revProps, 0,
                            callback);
-        Map<String, String> fetchedProps = callback.getRevprops();
+        Map<String, byte[]> fetchedProps = callback.getRevprops();
 
         assertEquals("wrong number of fetched revprops", revprops.size(),
                      fetchedProps.size());
@@ -3519,7 +3519,7 @@
         for (String key : keys)
           {
             assertEquals("revprops check", revprops.get(key),
-                         fetchedProps.get(key));
+                         new String(fetchedProps.get(key)));
           }
     }
 
@@ -3613,15 +3613,15 @@
 
     private class MyProplistCallback implements ProplistCallback
     {
-        Map<String, Map<String, String>> propMap =
-                                    new HashMap<String, Map<String, String>>();
+        Map<String, Map<String, byte[]>> propMap =
+                                    new HashMap<String, Map<String, byte[]>>();
 
-        public void singlePath(String path, Map<String, String> props)
+        public void singlePath(String path, Map<String, byte[]> props)
         {
             propMap.put(path, props);
         }
 
-        public Map<String, String> getProperties(String path)
+        public Map<String, byte[]> getProperties(String path)
         {
             return propMap.get(path);
         }
@@ -3649,15 +3649,15 @@
     {
         class MyProplistCallback implements ProplistCallback
         {
-            Map<String, Map<String, String>> propMap =
-                                new HashMap<String, Map<String, String>>();
+            Map<String, Map<String, byte[]>> propMap =
+                                new HashMap<String, Map<String, byte[]>>();
 
-            public void singlePath(String path, Map<String, String> props)
+            public void singlePath(String path, Map<String, byte[]> props)
             {
                 propMap.put(path, props);
             }
 
-            public Map<String, String> getProperties(String path)
+            public Map<String, byte[]> getProperties(String path)
             {
                 return propMap.get(path);
             }
@@ -3667,7 +3667,7 @@
         client.properties(path, revision, revision, depth, changelists,
                 callback);
 
-        Map<String, String> propMap = callback.getProperties(path);
+        Map<String, byte[]> propMap = callback.getProperties(path);
         if (propMap == null)
             return new PropertyData[0];
         PropertyData[] props = new PropertyData[propMap.size()];
@@ -3675,7 +3675,8 @@
         int i = 0;
         for (String key : propMap.keySet())
         {
-            props[i] = new PropertyData(path, key, propMap.get(key));
+            props[i] = new PropertyData(path, key,
+                                        new String(propMap.get(key)));
             i++;
         }
 
@@ -3771,16 +3772,16 @@
 
             public void singleMessage(ChangePath[] changedPaths,
                                       long revision,
-                                      Map<String, String> revprops,
+                                      Map<String, byte[]> revprops,
                                       boolean hasChildren)
             {
-                String author = (String) revprops.get("svn:author");
-                String message = (String) revprops.get("svn:log");
+                String author = new String(revprops.get("svn:author"));
+                String message = new String(revprops.get("svn:log"));
                 long timeMicros;
 
                 try {
-                    LogDate date = new LogDate((String)
-                                                    revprops.get("svn:date"));
+                    LogDate date = new LogDate(new String(
+                                                    revprops.get("svn:date")));
                     timeMicros = date.getTimeMicros();
                 } catch (ParseException ex) {
                     timeMicros = 0;
@@ -3862,9 +3863,9 @@
         }
 
         public void singleLine(long lineNum, long rev,
-                               Map<String, String> revProps,
+                               Map<String, byte[]> revProps,
                                long mergedRevision,
-                               Map<String, String> mergedRevProps,
+                               Map<String, byte[]> mergedRevProps,
                                String mergedPath, String line,
                                boolean localChange)
             throws ClientException
@@ -3873,14 +3874,14 @@
 
             try {
                 singleLine(
-                    df.parse((String) revProps.get("svn:date")),
+                    df.parse(new String(revProps.get("svn:date"))),
                     rev,
-                    (String) revProps.get("svn:author"),
+                    new String(revProps.get("svn:author")),
                     mergedRevProps == null ? null
-                        : df.parse((String) mergedRevProps.get("svn:date")),
+                        : df.parse(new String(mergedRevProps.get("svn:date"))),
                     mergedRevision,
                     mergedRevProps == null ? null
-                        : (String) mergedRevProps.get("svn:author"),
+                        : new String(mergedRevProps.get("svn:author")),
                     mergedPath, line);
             } catch (ParseException e) {
                 throw ClientException.fromException(e);



RE: svn commit: r917772 - in /subversion/trunk/subversion/bindings/javahl: native/ src/org/apache/subversion/javahl/callback/ src/org/tigris/subversion/javahl/ tests/org/apache/subversion/javahl/

Posted by Daniel Shahaf <d....@daniel.shahaf.name>.
Bert Huijben wrote on Tue, 2 Mar 2010 at 09:39 +0100:
> > Maybe the svn:date we can presume a UTF-8 character set, but the
> > svn:author, svn:log we shouldn't.
> 
> For the svn:* properties we currently define we declared that they
> always use utf-8 and use '\n' as line ending. Clients are responsible
> for handling the conversions. See svn_prop_needs_translation() for
> more details. (Since 1.6 we even validate this on the filesystem or ra
> layer). 
> 

The validation of properties is done in the repos layer, actually.
The FS layer doesn't assume that properties are in UTF-8.  (It does
assume/enforce that pathnames inside the repository are in UTF-8.)

> For other properties and svn:* we haven't defined yet, we can't assume
> anything. Users might have their MP3 collection stored in them ;-)
> (Most clients I know use these same normalization rules on all
> properties they edit. E.g. TortoiseSVN doesn't support editing
> properties with Windows style line endings)
> 
> 	Bert
> 
> 

RE: svn commit: r917772 - in /subversion/trunk/subversion/bindings/javahl: native/ src/org/apache/subversion/javahl/callback/ src/org/tigris/subversion/javahl/ tests/org/apache/subversion/javahl/

Posted by Daniel Shahaf <d....@daniel.shahaf.name>.
Bert Huijben wrote on Tue, 2 Mar 2010 at 09:39 +0100:
> > Maybe the svn:date we can presume a UTF-8 character set, but the
> > svn:author, svn:log we shouldn't.
> 
> For the svn:* properties we currently define we declared that they
> always use utf-8 and use '\n' as line ending. Clients are responsible
> for handling the conversions. See svn_prop_needs_translation() for
> more details. (Since 1.6 we even validate this on the filesystem or ra
> layer). 
> 

The validation of properties is done in the repos layer, actually.
The FS layer doesn't assume that properties are in UTF-8.  (It does
assume/enforce that pathnames inside the repository are in UTF-8.)

> For other properties and svn:* we haven't defined yet, we can't assume
> anything. Users might have their MP3 collection stored in them ;-)
> (Most clients I know use these same normalization rules on all
> properties they edit. E.g. TortoiseSVN doesn't support editing
> properties with Windows style line endings)
> 
> 	Bert
> 
> 

Re: svn commit: r917772 - in /subversion/trunk/subversion/bindings/javahl: native/ src/org/apache/subversion/javahl/callback/ src/org/tigris/subversion/javahl/ tests/org/apache/subversion/javahl/

Posted by Blair Zajac <bl...@orcaware.com>.
Bert Huijben wrote:
> 
>> -----Original Message-----
>> From: Blair Zajac [mailto:blair@orcaware.com]
>> Sent: dinsdag 2 maart 2010 1:47
>> To: hwright@apache.org
>> Cc: dev@subversion.apache.org
>> Subject: Re: svn commit: r917772 - in
>> /subversion/trunk/subversion/bindings/javahl: native/
>> src/org/apache/subversion/javahl/callback/ src/org/tigris/subversion/javahl/
>> tests/org/apache/subversion/javahl/
>>
>> On 03/01/2010 02:46 PM, hwright@apache.org wrote:
>>> Author: hwright
>>> Date: Mon Mar  1 22:46:45 2010
>>> New Revision: 917772
>>>
>>> URL: http://svn.apache.org/viewvc?rev=917772&view=rev
>>> Log:
>>> JavaHL: Return properties as byte[] throughout the callback interfaces.
>>>
>>> We use byte[] in place of String because there could be binary data in the
>>> property, and the conversion to String would truncate the property at any
>>> NULL bytes.
>>
>> Plus the conversion from byte[] to String depends upon the platform's
>> default character set.
>>
>> I see there's a number of String's constructed from the byte[].  Those
>> methods should take an additional java.nio.charset.Charset and then pass
>> it's name to the String() constructor.  I don't believe there should be
>> any String's constructed without a Charset argument.
>>
>> Maybe the svn:date we can presume a UTF-8 character set, but the
>> svn:author, svn:log we shouldn't.
> 
> For the svn:* properties we currently define we declared that they always use utf-8 and use '\n' as line ending. Clients are responsible for handling the conversions. See svn_prop_needs_translation() for more details. (Since 1.6 we even validate this on the filesystem or ra layer). 

OK.  So I believe we should add "UTF-8" as an additional constructor parameter 
to String for the svn:* properties.

Blair

RE: svn commit: r917772 - in /subversion/trunk/subversion/bindings/javahl: native/ src/org/apache/subversion/javahl/callback/ src/org/tigris/subversion/javahl/ tests/org/apache/subversion/javahl/

Posted by Bert Huijben <be...@qqmail.nl>.

> -----Original Message-----
> From: Blair Zajac [mailto:blair@orcaware.com]
> Sent: dinsdag 2 maart 2010 1:47
> To: hwright@apache.org
> Cc: dev@subversion.apache.org
> Subject: Re: svn commit: r917772 - in
> /subversion/trunk/subversion/bindings/javahl: native/
> src/org/apache/subversion/javahl/callback/ src/org/tigris/subversion/javahl/
> tests/org/apache/subversion/javahl/
> 
> On 03/01/2010 02:46 PM, hwright@apache.org wrote:
> > Author: hwright
> > Date: Mon Mar  1 22:46:45 2010
> > New Revision: 917772
> >
> > URL: http://svn.apache.org/viewvc?rev=917772&view=rev
> > Log:
> > JavaHL: Return properties as byte[] throughout the callback interfaces.
> >
> > We use byte[] in place of String because there could be binary data in the
> > property, and the conversion to String would truncate the property at any
> > NULL bytes.
> 
> 
> Plus the conversion from byte[] to String depends upon the platform's
> default character set.
> 
> I see there's a number of String's constructed from the byte[].  Those
> methods should take an additional java.nio.charset.Charset and then pass
> it's name to the String() constructor.  I don't believe there should be
> any String's constructed without a Charset argument.
> 
> Maybe the svn:date we can presume a UTF-8 character set, but the
> svn:author, svn:log we shouldn't.

For the svn:* properties we currently define we declared that they always use utf-8 and use '\n' as line ending. Clients are responsible for handling the conversions. See svn_prop_needs_translation() for more details. (Since 1.6 we even validate this on the filesystem or ra layer). 

For other properties and svn:* we haven't defined yet, we can't assume anything. Users might have their MP3 collection stored in them ;-)
(Most clients I know use these same normalization rules on all properties they edit. E.g. TortoiseSVN doesn't support editing properties with Windows style line endings)

	Bert

Re: svn commit: r917772 - in /subversion/trunk/subversion/bindings/javahl: native/ src/org/apache/subversion/javahl/callback/ src/org/tigris/subversion/javahl/ tests/org/apache/subversion/javahl/

Posted by Blair Zajac <bl...@orcaware.com>.
On 03/01/2010 02:46 PM, hwright@apache.org wrote:
> Author: hwright
> Date: Mon Mar  1 22:46:45 2010
> New Revision: 917772
>
> URL: http://svn.apache.org/viewvc?rev=917772&view=rev
> Log:
> JavaHL: Return properties as byte[] throughout the callback interfaces.
>
> We use byte[] in place of String because there could be binary data in the
> property, and the conversion to String would truncate the property at any
> NULL bytes.


Plus the conversion from byte[] to String depends upon the platform's 
default character set.

I see there's a number of String's constructed from the byte[].  Those 
methods should take an additional java.nio.charset.Charset and then pass 
it's name to the String() constructor.  I don't believe there should be 
any String's constructed without a Charset argument.

Maybe the svn:date we can presume a UTF-8 character set, but the 
svn:author, svn:log we shouldn't.

Regards,
Blair