Essential ActionScript 3.0


Essential ActionScript 3.0 Other resources from O’Reilly Related titles ActionScript 3.0 Design Patterns Dynamic HTML: The Definitive Reference Ajax on Java Ajax on Rails Learning JavaScript Programming Atlas Head Rush Ajax Rails Cookbook oreilly.com oreilly.com is more than a complete catalog of O’Reilly books. You'll also find links to news, events, articles, weblogs, sample chapters, and code examples. oreillynet.com is the essential portal for developers interested in open and emerging technologies, including new platforms, pro- gramming languages, and operating systems. Conferences O’Reilly brings diverse innovators together to nurture the ideas that spark revolutionary industries. We specialize in document- ing the latest tools and systems, translating the innovator’s knowledge into useful skills for those in the trenches. Visit con- ferences.oreilly.com for our upcoming events. Safari Bookshelf (safari.oreilly.com) is the premier online refer- ence library for programmers and IT professionals. Conduct searches across more than 1,000 books. Subscribers can zero in on answers to time-critical questions in a matter of seconds. Read the books on your Bookshelf from cover to cover or sim- ply flip to the page you need. Try it today with a free trial. Essential ActionScript 3.0 Colin Moock Beijing • Cambridge • Farnham • Köln • Paris • Sebastopol • Taipei • Tokyo Essential ActionScript 3.0 by Colin Moock Copyright © 2007 O’Reilly Media, Inc. All rights reserved. Printed in the United States of America. Published by O’Reilly Media, Inc., 1005 Gravenstein Highway North, Sebastopol, CA 95472. O’Reilly books may be purchased for educational, business, or sales promotional use. Online editions are also available for most titles (safari.oreilly.com). For more information, contact our corporate/institutional sales department: (800) 998-9938 or corporate@oreilly.com. Editor: Steve Weiss Developmental Editor: Robyn G. Thomas Production Editor: Philip Dangler Proofreader: Mary Anne Weeks Mayo Indexer: John Bickelhaupt Cover Designer: Karen Montgomery Interior Designer: David Futato Illustrators: Robert Romano and Jessamyn Read Printing History: August 2007: First Edition. Nutshell Handbook, the Nutshell Handbook logo, and the O’Reilly logo are registered trademarks of O’Reilly Media, Inc. Essential ActionScript 3.0, the image of a coral snake, and related trade dress are trademarks of O’Reilly Media, Inc. Many of the designations used by manufacturers and sellers to distinguish their products are claimed as trademarks. Where those designations appear in this book, and O’Reilly Media, Inc. was aware of a trademark claim, the designations have been printed in caps or initial caps. While every precaution has been taken in the preparation of this book, the publisher and author assume no responsibility for errors or omissions, or for damages resulting from the use of the information contained herein. This book uses RepKover™, a durable and flexible lay-flat binding. ISBN-10: 0-596-52694-6 ISBN-13: 978-0-596-52694-8 [M] v Table of Contents Foreword . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . xv Preface . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . xix Part I. ActionScript from the Ground Up 1. Core Concepts . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 3 Tools for Writing ActionScript Code 3 Flash Client Runtime Environments 4 Compilation 5 Quick Review 6 Classes and Objects 6 Creating a Program 8 Packages 9 Defining a Class 11 Virtual Zoo Review 13 Constructor Methods 14 Creating Objects 16 Variables and Values 19 Constructor Parameters and Arguments 24 Expressions 26 Assigning One Variable’s Value to Another 28 An Instance Variable for Our Pet 30 Instance Methods 31 Members and Properties 42 Virtual Zoo Review 42 Break Time! 43 vi | Table of Contents 2. Conditionals and Loops . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 44 Conditionals 44 Loops 50 Boolean Logic 58 Back to Classes and Objects 62 3. Instance Methods Revisited . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 63 Omitting the this Keyword 64 Bound Methods 66 Using Methods to Examine and Modify an Object’s State 68 Get and Set Methods 72 Handling an Unknown Number of Parameters 75 Up Next: Class-Level Information and Behavior 76 4. Static Variables and Static Methods . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 77 Static Variables 77 Constants 80 Static Methods 82 Class Objects 85 C++ and Java Terminology Comparison 86 On to Functions 86 5. Functions . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 87 Package-Level Functions 88 Nested Functions 90 Source-File-Level Functions 91 Accessing Definitions from Within a Function 92 Functions as Values 93 Function Literal Syntax 93 Recursive Functions 95 Using Functions in the Virtual Zoo Program 96 Back to Classes 100 6. Inheritance . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 101 A Primer on Inheritance 101 Overriding Instance Methods 105 Constructor Methods in Subclasses 108 Preventing Classes from Being Extended and Methods from Being Overridden 112 Table of Contents | vii Subclassing Built-in Classes 113 The Theory of Inheritance 114 Abstract Not Supported 120 Using Inheritance in the Virtual Zoo Program 121 Virtual Zoo Program Code 126 It’s Runtime! 129 7. Compiling and Running a Program . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 130 Compiling with the Flash Authoring Tool 130 Compiling with Flex Builder 2 131 Compiling with mxmlc 133 Compiler Restrictions 134 The Compilation Process and the Classpath 134 Strict-Mode Versus Standard-Mode Compilation 135 The Fun’s Not Over 136 8. Datatypes and Type Checking . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 137 Datatypes and Type Annotations 138 Untyped Variables, Parameters, Return Values, and Expressions 142 Strict Mode’s Three Special Cases 143 Warnings for Missing Type Annotations 144 Detecting Reference Errors at Compile Time 145 Casting 146 Conversion to Primitive Types 150 Default Variable Values 153 null and undefined 153 Datatypes in the Virtual Zoo 154 More Datatype Study Coming Up 158 9. Interfaces . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 159 The Case for Interfaces 159 Interfaces and Multidatatype Classes 161 Interface Syntax and Use 162 Another Multiple-Type Example 165 More Essentials Coming 171 10. Statements and Operators . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 172 Statements 172 Operators 174 Up Next: Managing Lists of Information 185 viii | Table of Contents 11. Arrays . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 186 What Is an Array? 186 The Anatomy of an Array 187 Creating Arrays 187 Referencing Array Elements 189 Determining the Size of an Array 191 Adding Elements to an Array 193 Removing Elements from an Array 197 Checking the Contents of an Array with the toString( ) Method 199 Multidimensional Arrays 200 On to Events 201 12. Events and Event Handling . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 202 ActionScript Event Basics 202 Accessing the Target Object 209 Accessing the Object That Registered the Listener 212 Preventing Default Event Behavior 213 Event Listener Priority 214 Event Listeners and Memory Management 216 Custom Events 221 Type Weakness in ActionScript’s Event Architecture 233 Handling Events Across Security Boundaries 236 What’s Next? 240 13. Exceptions and Error Handling . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 241 The Exception-Handling Cycle 241 Handling Multiple Types of Exceptions 244 Exception Bubbling 253 The finally Block 258 Nested Exceptions 260 Control-Flow Changes in try/catch/finally 264 Handling a Built-in Exception 267 More Gritty Work Ahead 268 14. Garbage Collection . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 269 Eligibility for Garbage Collection 269 Incremental Mark and Sweep 272 Disposing of Objects Intentionally 273 Deactivating Objects 274 Table of Contents | ix Garbage Collection Demonstration 277 On to ActionScript Backcountry 278 15. Dynamic ActionScript . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 279 Dynamic Instance Variables 280 Dynamically Adding New Behavior to an Instance 284 Dynamic References to Variables and Methods 286 Using Dynamic Instance Variables to Create Lookup Tables 287 Using Functions to Create Objects 289 Using Prototype Objects to Augment Classes 291 The Prototype Chain 292 Onward! 294 16. Scope . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 295 Global Scope 296 Class Scope 297 Static Method Scope 298 Instance Method Scope 298 Function Scope 299 Scope Summary 300 The Internal Details 300 Expanding the Scope Chain via the with Statement 302 On to Namespaces 303 17. Namespaces . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 304 Namespace Vocabulary 304 ActionScript Namespaces 305 Creating Namespaces 307 Using a Namespace to Qualify Variable and Method Definitions 310 Qualified Identifiers 312 A Functional Namespace Example 314 Namespace Accessibility 317 Qualified-Identifier Visibility 321 Comparing Qualified Identifiers 322 Assigning and Passing Namespace Values 323 Open Namespaces and the use namespace Directive 334 Namespaces for Access-Control Modifiers 338 Applied Namespace Examples 341 Final Core Topics 352 x | Table of Contents 18. XML and E4X . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 353 Understanding XML Data as a Hierarchy 353 Representing XML Data in E4X 355 Creating XML Data with E4X 357 Accessing XML Data 359 Processing XML with for-each-in and for-in 377 Accessing Descendants 379 Filtering XML Data 383 Traversing XML Trees 386 Changing or Creating New XML Content 387 Loading XML Data 397 Working with XML Namespaces 398 Converting XML and XMLList to a String 404 Determining Equality in E4X 407 More to Learn 410 19. Flash Player Security Restrictions . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 411 What’s Not in This Chapter 412 The Local Realm, the Remote Realm, and Remote Regions 412 Security-Sandbox-Types 413 Security Generalizations Considered Harmful 415 Restrictions on Loading Content, Accessing Content as Data, Cross-Scripting, and Loading Data 416 Socket Security 422 Example Security Scenarios 422 Choosing a Local Security-Sandbox-Type 425 Distributor Permissions (Policy Files) 429 Creator Permissions (allowDomain( )) 444 Import Loading 446 Handling Security Violations 448 Security Domains 450 Two Common Security-Related Development Issues 452 On to Part II! 454 Table of Contents | xi Part II. Display and Interactivity 20. The Display API and the Display List . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 457 Display API Overview 458 The Display List 462 Containment Events 487 Custom Graphical Classes 499 Go with the Event Flow 501 21. Events and Display Hierarchies . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 502 Hierarchical Event Dispatch 502 Event Dispatch Phases 503 Event Listeners and the Event Flow 505 Using the Event Flow to Centralize Code 511 Determining the Current Event Phase 514 Distinguishing Events Targeted at an Object from Events Targeted at That Object’s Descendants 516 Stopping an Event Dispatch 518 Event Priority and the Event Flow 522 Display-Hierarchy Mutation and the Event Flow 523 Custom Events and the Event Flow 526 On to Input Events 530 22. Interactivity . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 531 Mouse-Input Events 532 Focus Events 548 Keyboard-Input Events 555 Text-Input Events 565 Flash Player-Level Input Events 580 From the Program to the Screen 586 23. Screen Updates . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 587 Scheduled Screen Updates 587 Post-Event Screen Updates 596 Redraw Region 600 Optimization with the Event.RENDER Event 601 Let’s Make It Move! 609 xii | Table of Contents 24. Programmatic Animation . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 610 No Loops 610 Animating with the ENTER_FRAME Event 611 Animating with the TimerEvent.TIMER Event 616 Choosing Between Timer and Event.ENTER_FRAME 623 A Generalized Animator 624 Velocity-Based Animation 627 Moving On to Strokes ’n’ Fills 628 25. Drawing with Vectors . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 629 Graphics Class Overview 629 Drawing Lines 630 Drawing Curves 633 Drawing Shapes 634 Removing Vector Content 636 Example: An Object-Oriented Shape Library 637 From Lines to Pixels 647 26. Bitmap Programming . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 648 The BitmapData and Bitmap Classes 649 Pixel Color Values 649 Creating a New Bitmap Image 654 Loading an External Bitmap Image 656 Examining a Bitmap 658 Modifying a Bitmap 664 Copying Graphics to a BitmapData Object 672 Applying Filters and Effects 686 Freeing Memory Used by Bitmaps 694 Words, Words, Words 695 27. Text Display and Input . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 696 Creating and Displaying Text 699 Modifying a Text Field’s Content 705 Formatting Text Fields 708 Fonts and Text Rendering 735 Missing Fonts and Glyphs 748 Determining Font Availability 749 Determining Glyph Availability 751 Embedded-Text Rendering 752 Table of Contents | xiii Text Field Input 755 Text Fields and the Flash Authoring Tool 759 Loading...Please Wait... 761 28. Loading External Display Assets . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 762 Using Loader to Load Display Assets at Runtime 763 Compile-Time Type-Checking for Runtime-Loaded Assets 781 Accessing Assets in Multiframe .swf Files 790 Instantiating a Runtime-Loaded Asset 793 Using Socket to Load Display Assets at Runtime 796 Removing Runtime Loaded .swf Assets 806 Embedding Display Assets at CompileTime 807 On to Part III 818 Part III. Applied ActionScript Topics 29. ActionScript and the Flash Authoring Tool . . . . . . . . . . . . . . . . . . . . . . . . . . . 821 The Flash Document 821 Timelines and Frames 822 Timeline Scripting 826 The Document Class 828 Symbols and Instances 832 Linked Classes for Movie Clip Symbols 834 Accessing Manually Created Symbol Instances 838 Accessing Manually Created Text 844 Programmatic Timeline Control 845 Instantiating Flash Authoring Symbols via ActionScript 847 Instance Names for Programmatically Created Display Objects 848 Linking Multiple Symbols to a Single Superclass 849 The Composition-Based Alternative to Linked Classes 851 Preloading Classes 852 Up Next: Using the Flex Framework 855 xiv | Table of Contents 30. A Minimal MXML Application . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 856 The General Approach 856 A Real UI Component Example 859 Sharing with Your Friends 860 31. Distributing a Class Library . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 861 Sharing Class Source Files 862 Distributing a Class Library as a .swc File 863 Distributing a Class Library as a .swf File 867 But Is It Really Over? 873 Appendix . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 875 Index . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 891 xv Foreword 1 We imagine a world where every digital interaction—whether in the classroom, the office, the living room, the airport, or the car—is a powerful, simple, efficient, and engaging experience. Flash Player is widely used to deliver these experiences and has evolved into a sophisticated platform across browsers, operating systems, and devices. One of the main forces driving Adobe’s innovation and the development of the Flash Player is seeing where developers are pushing the edge of what’s possible to imple- ment, and then enabling more developers to accomplish that kind of work. Taking the way-back machine to 2001, you would see the web being widely used and the early signs of web sites containing not only pages but also interactive applica- tions. These applications were primarily using HTML forms and relying on web serv- ers for processing the form information. A handful of leading edge developers were working to implement a more responsive interaction by taking advantage of client- side processing with ActionScript in Flash. One of the earliest examples of successful interactive applications was the hotel reservation system for the Broadmoor Hotel, which moved from a multi-page HTML form to a one-screen, highly interactive res- ervation interface that increased their online reservations by 89%. Clearly, responsiveness matters. It creates a much more effective, engaging experi- ence. However, in 2001, there was a lot to be desired in terms of performance, power of the scripting language, ease of debugging, and design constraints within browsers (which were created to view pages rather than host applications). We did a lot of brainstorming and talked extensively to developers and decided to embark on a mission to enable this trend, naming the category “Rich Internet Appli- cations” (RIAs). To better support RIAs, we aimed to create: • A tremendously faster virtual machine in Flash Player for ActionScript 3.0. • A development framework called Flex, making it radically easier to build RIAs. • An environment specifically to deliver rich Internet applications to their full potential, known now as the Adobe Integrated Runtime (AIR). During the dot- com bust, we held onto the vision of enabling this future world of rich Internet applications. xvi | Foreword We continued to invest in building a range of technologies and prepared for the day that innovation on the web would ignite again. The days of innovation have now returned in full force, and I am delighted to see rich Internet applications coming into their own with Web 2.0. Developers are creating applications with a range of technologies and frameworks that tap into the distributed creativity of the Internet, take advantage of HTML, Flash, Flex, Ajax; and balance logic between the client and server. The new virtual machine has been delivered now in Flash Player 9, enabling Action- Script 3.0 to run an order of magnitude faster and implement the most recent work on the ECMA standard for the language (JavaScript follows this same standard). This modern implementation has also now been released as open source with the Mozilla Foundation as the Tamarin project, enabling the Flash Player team to work with Mozilla engineers and others in the open source community to continue optimizing the virtual machine and keeping up with the most recent standards work. This core scripting engine will be incorporated over time in Firefox, bringing consistency across scripting in HTML and Flash. The development framework has also been delivered today as Flex, enabling rapid development through common patterns for interaction and data management, with the whole framework built in ActionScript 3.0. The Flexframework is available for free, and the framework source code is included so you can see exactly how it works. You can use any editor to write code using Flex, and a specific IDE is also available, called Adobe Flex Builder. As we saw innovation on the web returning and were pursuing this vision, we decided to unite efforts across Adobe and Macromedia. While Macromedia was driv- ing RIAs with Flash, Adobe was innovating in delivery of electronic documents, among other areas. We saw over time that Macromedia would be adding electronic document capability to RIAs and that Adobe would add RIA capability around elec- tronic documents. Rather than pursue those paths separately and duplicate efforts, we joined forces to deliver our vision for the next generation of documents and RIAs, bringing together the world’s best technology for electronic documents and the world’s best, most pervasive technology for RIAs. It’s an incredibly powerful combination. After we announced the merger, we created a “clean room” team to plan for our next generation of software, drawing on everything we’ve learned to date as well as from the potential of bringing Flash, PDF, and HTML together in the new Adobe AIR environment for RIAs. The AIR project is actually our third attempt at creating this new environment. The first two attempts were part of an experimental project called Central which was code named Mercury and then Gemini after the United States space program, and with AIR code named Apollo. We learned a lot from those first two projects, and as I like to remind the team, Apollo is the one that actually went to the moon. Foreword | xvii With AIR, you can leverage your existing web development skills (Flash, Flex, HTML, JavaScript, Ajax) to build and deploy RIAs to the desktop. Just like web pub- lishing allowed anyone with basic HTML skills to create a web site, AIR will enable anyone with basic web development skills to create a desktop application. As a developer, you can now create a closer connection to your users. With the browser, you have a fleeting, somewhat tenuous, connection to users. They browse to a page, and then they’re gone. AIR enables you to create an experience that can keep you continuously connected to your customers. Just like a desktop application, AIR applications have an icon on the desktop, in the Windows start menu, or in the OS X dock. Also, when you’re running a web application today, it’s a separate world from your computer. You can’t easily integrate local data with your web application. For example, you can’t just drag and drop your Outlook contacts onto a web-based mapping application to get directions to your friend’s house. Yet with AIR applica- tions you can, as it bridges the chasm between your computer and the Internet. I believe AIR represents the beginning of a new medium. And these applications are fun to build. If you start early, you’ll be able to deliver capabilities in your applica- tions that others won’t have yet—especially in terms of increasing the presence of your application on the computer and bridging the web and the desktop. The core of these RIAs is the ActionScript language, whether they run in the Flash Player in a browser, as a desktop application through AIR, or on mobile devices. Each generation of the ActionScript language has been comprehensively described by Colin Moock in this series of O’Reilly books, becoming the reference book you’ll find on most Flash developer’s desks. With ActionScript 3.0, you have unprece- dented power in building engaging applications and with this reference you have tre- mendous insight to use that power effectively. I look forward to seeing what you create and to the next generation of applications ahead. Keep pushing the boundaries of what’s possible on the Internet to make the experience more engaging and effective for people around the world, and we will do our best to continue bringing more expressiveness and power to help you in your efforts. —Kevin Lynch Chief Software Architect, Adobe San Francisco, 2007 xix -Ch Preface ActionScript is the official programming language of Adobe’s Flash platform. While originally conceived as a simple tool for controlling animation, ActionScript has since evolved into a sophisticated programming language for creating content and applications for the Web, mobile devices, and desktop computers. True to its roots, ActionScript can be used in many different ways by many different kinds of program- mers and content producers. For example, an animator might use just a few lines of ActionScript to pause the playback of a web animation. Or, an interface designer might use a few hundred lines of ActionScript to add interactivity to a mobile phone interface. Or, an application developer might use thousands of lines of ActionScript to create an entire email-reading application for web browser and desktop deployment. This book covers ActionScript programming fundamentals in truly exhaustive detail, with extreme clarity and precision. Its unparalleled accuracy and depth is the result of an entire decade of daily ActionScript research, real-world programming experi- ence, and unmitigated insider-access to Adobe’s engineers. Every word of this book has been carefully reviewed—in many cases several times over—by key members of Adobe’s engineering staff, including those on the Flash Player, FlexBuilder, and Flash authoring teams. (See the “Acknowledgments” section at the end of this preface.) Beginners Welcome This book explores ActionScript from a programmer’s perspective but assumes no prior programming knowledge. If you have never programmed before, start with Chapter 1. It will guide you through the very basics of ActionScript, demystifying terms like variable, method, class, and object. Then continue through the book sequentially. Each chapter builds on the previous chapter’s concepts, introducing new topics in a single, prolonged narrative that will guide you on your journey to ActionScript proficiency. xx | Preface Note, however, that if you are a designer who simply wants to learn how to control animations in the Flash authoring tool, you probably don’t need this book. Adobe’s documentation will tell what you need to know. Come back to this book when you want to learn how to add logic and programmatic behavior to your content. Expert Guidance If you already have existing ActionScript experience, this book will help you fill in gaps in your knowledge, rethink important concepts in formal terms, and under- stand difficult subjects through plain, careful language. Consider this book an ActionScript expert that sits with you at your desk. You might ask it to explain the subtleties of ActionScript’s event architecture, or unravel the intricacies of Flash Player’s security system, or demonstrate the power of ActionScript’s native XML support (E4X). Or you might turn to this book for information on under-docu- mented topics, such as namespaces, embedded fonts, loaded-content access, class- library distribution, garbage collection, and screen updates. This book is a true developer’s handbook, packed with practical explanations, insightful warnings, and useful example code that demonstrates how to get the job done right. What’s In This Book This book is divided into three parts. Part I, ActionScript from the Ground Up, provides exhaustive coverage of the core ActionScript language, covering object-oriented programming, classes, objects, vari- ables, methods, functions, inheritance, datatypes, arrays, events, exceptions, scope, namespaces, XML. Part I closes with a look at Flash Player’s security architecture. Part II, Display and Interactivity, explores techniques for displaying content on screen and responding to input events. Topics covered include the Flash runtime dis- play API, hierarchical event handling, mouse and keyboard interactivity, animation, vector graphics, bitmap graphics, text, and content loading operations. Part III, Applied ActionScript Topics, focuses on ActionScript code-production issues. Topics covered include combining ActionScript with assets created manually in the Flash authoring tool, using the Flexframework in FlexBuilder 2, and creating a cus- tom code library. This book closes with a walkthrough of a fully functional example program—a vir- tual zoo. Preface | xxi What’s Not In This Book The ActionScript ecosystem is vast. No single book can cover it all. Noteworthy top- ics that are not covered extensively in this book include: • MXML • The Flex framework • Flex Data Services • The Flash authoring tool’s built-in components • Flash Media Server • Flash Remoting • ActionScript’s regular expression support For information on these topics, see Adobe’s documentation and O’Reilly’s Adobe Developer Library, at http://www.oreilly.com/store/series/adl.csp. Authoring Tool Agnosticism This book teaches core ActionScript concepts that apply to any ActionScript 3.0 authoring environment and any runtime that supports ActionScript 3.0. As much as possible, this book avoids tool-specific development topics and focuses on program- ming concepts rather than tool usage. That said, Chapter 29 covers ActionScript’s use in the Flash authoring tool, and Chapter 30 covers the very basics of using the Flexframework in FlexBuilder 2. Likewise, Chapter 7 describes how to compile a program using various authoring tools (Flash, Flex Builder 2, and mxmlc). Now let’s turn our attention to the ActionScript language itself. The following sec- tions provide a technical introduction to ActionScript 3.0 for experienced program- mers. If you are completely new to programming, you should skip down to “Typographical Conventions” and then proceed to Chapter 1. ActionScript Overview ActionScript 3.0 is an object-oriented language for creating applications and scripted multimedia content for playback in Flash client runtimes (such as Flash Player and Adobe AIR). With a syntaxreminiscent of Java and C#, ActionScript’s core lan- guage should be familiar to experienced programmers. For example, the following code creates a variable named width, of type int (meaning integer), and assigns it the value 25: var width:int = 25; xxii | Preface The following code creates a for loop that counts up to 10: for (var i:int = 1; i <= 10; i++) { // Code here runs 10 times } And the following code creates a class named Product: // The class definition public class Product { // An instance variable of type Number var price:Number; // The Product class constructor method public function Product ( ) { // Code here initializes Product instances } // An instance method public function doSomething ( ):void { // Code here executes when doSomething( ) is invoked } } The Core Language ActionScript 3.0’s core language is based on the ECMAScript 4th edition language specification, which is still under development as of May 2007. The ECMAScript 4 specification can be viewed at http://developer. mozilla.org/es4/spec/spec.html. The ActionScript 3.0 specification can be viewed at http://livedocs.macromedia.com/specs/actionscript/3. In the future, ActionScript is expected to be a fully conforming implementation of ECMAScript 4. Like ActionScript, the popular web browser language JavaScript is also based on ECMAScript. The future Firefox3.0 web browser is expectedto imple- ment JavaScript 2.0 using the same code base as ActionScript, which was contrib- uted to the Mozilla Foundation by Adobe in November 2006 (for information, see http://www.mozilla.org/projects/tamarin). ECMAScript 4 dictates ActionScript’s basic syntaxand grammar—the code used to create things such as expressions, statements, variables, functions, classes, and objects. ECMAScript 4 also defines a small set of built-in datatypes for working with common values (such as String, Number, and Boolean). Some of ActionScript 3.0’s key core-language features include: • First-class support for common object-oriented constructs, such as classes, objects, and interfaces • Single-threaded execution model Preface | xxiii • Runtime type-checking • Optional compile-time type-checking • Dynamic features such as runtime creation of new constructor functions and variables • Runtime exceptions • Direct support for XML as a built-in datatype • Packages for organizing code libraries • Namespaces for qualifying identifiers • Regular expressions All Flash client runtimes that support ActionScript 3.0 share the features of the core language in common. This book covers the core language in its entirety, save for reg- ular expressions. Flash Runtime Clients ActionScript programs can be executed in three different client runtime environ- ments: Adobe AIR, Flash Player, and Flash Lite. Adobe AIR Adobe AIR runs Flash-platform applications intended for desktop deployment. Adobe AIR supports SWF-format content, as well as content produced with HTML and JavaScript. Adobe AIR must be installed directly on the end user’s computer at the operating-system level. For more information, see http://www.adobe.com/go/air. Flash Player Flash Player runs Flash-platform content and applications intended for web deployment. Flash Player is the runtime of choice for embedding SWF-format content on a web page. Flash Player is typically installed as a web browser add- on but can also run in standalone mode. Flash Lite Flash Lite runs Flash-platform content and applications intended for mobile- device deployment. Due to the performance limitations of mobile devices, Flash Lite typically lags behind Flash Player and Adobe AIR in both speed and feature set. As of June 2007, Flash Lite does not yet support ActionScript 3.0. The preceding Flash client runtimes offer a common core set of functionality, plus a custom set of features that cater to the capabilities and security requirements of the runtime environment. For example, Adobe AIR, Flash Player, and Flash Lite all use the same syntaxfor creating a variable, but Adobe AIR includes window-manage- ment and filesystem APIs, Flash Lite can make a phone vibrate, and Flash Player imposes special web-centric security restrictions to protect the end user’s privacy. xxiv | Preface Runtime APIs Each Flash client runtime offers its own built-in set of functions, variables, classes, and objects—known as its runtime API. Each Flash client runtime’s API has its own name. For example, the Flash client runtime API defined by Flash Player is known as the Flash Player API. All Flash client runtime APIs share a core set of functionality in common. For exam- ple, every Flash client runtime uses the same basic set of classes for displaying con- tent on screen and for dispatching events. Key features shared by all Flash client runtime APIs include: • Graphics and video display • A hierarchical event architecture • Text display and input • Mouse and keyboard control • Network operations for loading external data and communicating with server- side applications • Audio playback • Printing • Communicating with external local applications • Programming utilities This book covers the first five of the preceding items. For information on other spe- cific Flash client runtime APIs, consult the appropriate product documentation. Components In addition to the Flash client runtime APIs, Adobe also offers two different sets of components for accomplishing common programming tasks and building user inter- faces. FlexBuilder 2 and the free Flex2 SDK include the Flex framework, which defines a complete set of user interface controls, such as RadioButton, CheckBox, and List. The Flash authoring tool provides a similar set of user interface components. The Flash authoring tool’s components combine code with manually created graphi- cal assets that can be customized by Flash developers and designers. Both the Flexframework and the Flash authoring tool’s component set are written entirely in ActionScript 3.0. The user interface components in the Flexframework generally have more features than those in the Flash authoring tool’s component set and, therefore, also have a larger file size. User interface components from the Flexframework cannot be used in the Flash authoring tool, but user interface components from the Flash authoring tool can be used (both legally and technically) with Flex Builder 2 and mxmlc. Preface | xxv This book does not cover component use or creation in ActionScript. For informa- tion on components, see the appropriate product documentation. The Flash File Format (SWF) ActionScript code must be compiled into a .swf file for playback in one of Adobe’s Flash client runtimes. A .swf file can include both ActionScript bytecode and embedded assets (graphics, sound, video, and fonts). Some .swf files contain assets only and no code, while others contain code only and no assets. A single Action- Script program might reside entirely within a single .swf file, or it might be broken into multiple .swf files. When a program is broken into multiple .swf files, one spe- cific .swf file provides the program point of entry, and loads the other .swf files as required. Breaking a complexprogram into multiple .swf files makes it easier to maintain and, for Internet-delivered applications, can give the user faster access to different sections of the program. ActionScript Development Tools Adobe offers the following tools for creating ActionScript code: Adobe Flash http://www.adobe.com/go/flash/ A visual design and programming tool for creating multimedia content that inte- grates graphics, video, audio, animation, and interactivity. In Adobe Flash, developers create interactive content by combining ActionScript code with ani- mation, manually created content, and embedded assets. Adobe Flash is also known as the Flash authoring tool. As of June 2007, the latest version of the Flash authoring tool is Flash CS3 (Version 9 of the software). Adobe Flex Builder http://www.adobe.com/products/flex/productinfo/overview/ A development tool for producing content using either pure ActionScript or MXML, an XML-based language for describing user interfaces. FlexBuilder includes a development framework known as the Flexframework, which pro- vides an extensive set of programming utilities and a library of skinnable, styleable user-interface controls. Based on Eclipse, the popular open source pro- gramming tool, FlexBuilder 2 can be used in either hand-coding mode or in a visual-development mode similar to Microsoft’s Visual Basic. Adobe Flex 2 SDK http://www.adobe.com/go/flex2_sdk A free command-line toolkit for creating content using either pure ActionScript 3.0 or MXML. The Flex2 SDK includes the Flexframework and a command- line compiler, mxmlc (both of which are also included with Adobe FlexBuilder 2). Using the Flex2 SDK, developers can create content for free in the xxvi | Preface programming editor of their choice. (For a wide variety of open source tools and utilities for ActionScript development, see http://osflash.org.) This Book’s Example Files The official companion web site for this book is: http://moock.org/eas3 You can download the example files for this book at: http://moock.org/eas3/examples Note that most of the examples in this book are presented in the context of an enclosing main class, which is intended to be compiled as a .fla file’s document class (Flash authoring tool) or a project’s default application class (Flex Builder). Using Code Examples This book is here to help you get your job done. In general, you can use the code in this book in your programs and documentation. You do not need to contact us for permission unless you’re reproducing a significant portion of the code. For example, writing a program that uses several chunks of code from this book does not require permission. Selling or distributing a CD-ROM of examples from O’Reilly books does require permission. Answering a question by citing this book and quoting example code does not require permission. Incorporating a significant amount of example code from this book into your product’s documentation does require permission. We appreciate, but do not require, attribution. An attribution usually includes the title, author, publisher, and ISBN. For example: “Essential ActionScript 3.0 by Colin Moock. Copyright 2007 O’Reilly Media, Inc., 0-596-52694-6”. If you feel your use of code examples falls outside fair use or the permission given above, feel free to contact us at permissions@oreilly.com. Typographical Conventions In order to indicate the various syntactic components of ActionScript, this book uses the following conventions: Menu options Menu options are shown using the ➝ character, such as File ➝ Open. Constant width Indicates code examples, code snippets, variable names, and parameter names. Preface | xxvii Italic Indicates function names, method names, class names, package names, URLs, filenames, datatypes, keywords, objects, and file suffixes such as .swf. In addi- tion to being italicized in the body text, method and function names are also fol- lowed by parentheses, such as duplicateMovieClip( ). Constant width bold Indicates text that you must enter verbatim when following a step-by-step proce- dure. Constant width bold is also sometimes used within code examples for emphasis, such as to highlight an important line of code in a larger example. Constant width italic Indicates code that you must replace with an appropriate value (e.g., yournamehere). This is a tip. It contains useful information about the topic at hand, often highlighting important concepts or best practices. This is a warning. It helps you solve and avoid annoying problems. Ignore at your own peril. This is a note about ActionScript 2.0. It compares and contrasts ActionScript 2.0 with ActionScript 3.0, helping you to migrate to ActionScript 3.0 and to understand important differences between the two versions of the language. Coding and vocabulary conventions used in this book include: • The keyword this is written in constant-width font because it is an implicit parameter passed to methods and functions. • In general, the keyword this is not included when making reference to identifi- ers from within instance methods. However, this is used to disambiguate instance variables and instance methods from parameters and local variables. • When discussing accessor methods and mutator methods, this book avoids the traditional terms accessor, mutator, getter, and setter. Instead, this book uses the unofficial terms retriever method and modifier method. See Chapter 3. • In a class definition that contains static variables, static methods, instance vari- ables, instance methods, and a constructor method, this book lists the static variables first, followed by the static methods, the instance variables, the class constructor method, and finally, the instance methods. • This book uses ALL CAPITAL LETTERS for constant names. xxviii | Preface • When referring to static variables and static methods, this book always includes the name of the class that defines the variable or method. • Unless otherwise stated, function closures are referred to by the shorter term function. See Chapter 5 for a description of the difference between the two terms. • This book assumes that all code is compiled in strict mode. Furthermore, after Chapter 7, this book supplies type annotations for all variables, parameters, and return values. • Event listeners in this book are named using the format eventNameListener, where eventName is the string name of the event. How to Contact Us We have tested and verified the information in this book to the best of our ability, but you may find that features have changed (or even that we have made mistakes!). Please let us know about any errors you find, as well as your suggestions for future editions, by writing to: O’Reilly Media, Inc. 1005 Gravenstein Highway North Sebastopol, CA 95472 (800) 998-9938 (in the United States or Canada) (707) 829-0515 (international/local) (707) 829-0104 (fax) We have a web page for the book, where we list errata, examples, or any additional information. You can access this page at: http://www.oreilly.com/catalog/9780596526948 To comment or ask technical questions about this book, send email to: bookquestions@oreilly.com For more information about our books, conferences, software, Resource Centers, and the O’Reilly Network, see our web site at: http://www.oreilly.com Preface | xxix Safari® Enabled When you see a Safari® Enabled icon on the cover of your favorite tech- nology book, it means the book is available online through the O’Reilly Network Safari Bookshelf. Safari offers a solution that’s better than e-books. It’s a virtual library that lets you easily search thousands of top technology books, cut and paste code samples, down- load chapters, and find quick answers when you need the most accurate, current information. Try it for free at http://safari.oreilly.com. Acknowledgments This book could not have been written without the abundant trust and very active support of both Adobe and O’Reilly. In the summer of 2005, in a meeting with Steve Weiss, Lisa Friendly, and Mike Chambers, I agreed to write a new book called Essential ActionScript 3.0. The book was originally billed as “a short update to Essential ActionScript 2.0.” But as the ActionScript 3.0 language evolved, Essential ActionScript 3.0 become a full work of its own. Patiently and faithfully, Adobe and O’Reilly watched the book increase vastly in scope, and agreed to let the publication deadline creep from nine months to two years. Throughout the entire process, I truly believed we were making the right choice, and I’m honored that Adobe and O’Reilly agreed. Throughout this book’s writing, Adobe graciously furnished me with full access to internal resources and official engineering time for technical reviews. I owe great thanks to JuLee Burdekin and Francis Cheng from Adobe’s documentation team. JuLee and Francis coordinated my efforts internally at Adobe and answered a seem- ingly endless stream of questions. Dozens of Adobe employees provided me with information and instruction during this book’s research. I am deeply grateful to all of them, and would especially like to thank the following: • Francis Cheng was a constant source of information on the core language and provided invaluable feedback on Essential ActionScript 3.0’s manuscript. Fran- cis sits on the ECMAScript 4 committee and is one of the authors of the Action- Script 3.0 specification. • Jeff Dyer consistently took time out of his schedule to help clarify core-language concepts and investigate bugs. Jeff is one of the principal developers of the ActionScript 3.0 compiler, the principal author of the ActionScript 3.0 specifica- tion, and a key member of the ECMAScript 4 committee. xxx | Preface • Deneb Meketa patiently endured my misunderstanding of Flash Player’s secu- rity system. Through phone calls and emails that spanned more than a month of intensive research, Deneb managed to bring clarity to Chapter 19. Deneb is the engineer responsible for implementing security in Flash Player. • Jeff Mott, Flash Player engineer, consistently offered extensive, near instant responses to my questions about ActionScript’s event system. • Jim Corbett, Flash Player engineer, helped me understand many display list and event-loading subtleties. • Rebecca Sun, Flash authoring engineer, answered many questions about the links between the ActionScript 3.0 compiler and Flash CS3. She also listened openly to suggestions, and endured my frequent spontaneous requests for infor- mation over instant messenger. • Lee Thomason, Flash Player Architect, gave me a personal education in Flash Player’s text rendering engine. • Roger Gonzalez, FlexCompiler Architect, regularly fielded my questions on class loading and the Flex compiler. • Werner Sharp, Flash Player engineer, explained many subtle nuances of bitmap programming in ActionScript. • Paul Betlem, Senior Director of Flash Player Engineering, helped facilitate the technical review process and reviewed several chapters himself. • Mike Chambers, Senior Product Manager of Developer Relations for Adobe AIR, provided regular technical information, and helped nurture the Essential Action- Script 3.0 project from its earliest stages. • Gary Grossman, the original creator of ActionScript, taught me much of what I know about programming for the Flash platform. In August 2006, Gary teamed up with the inventors of Flash (Jon Gay and Robert Tatsumi) to co-found a new company, Software as Art. See http://www.softwareasart.com. Other Adobe staff, past and present, that I’m honored to know and work with include Mike Downey, Kevin Lynch, Paul Betlem, Edwin Smith, Christine Yarrow, Jeff Kamerer, Nigel Pegg, Matt Wobensmith, Thomas Reilly, Jethro Villegas, Rob Dixon, Jeff Swartz, Waleed Anbar, Chris Thilgen, Gilles Drieu, Nivesh Rajbhandari, Tei Ota, Akio Tanaka, Sumi Lim, Troy Evans, John Dowdell, Bentley Wolfe, Tinic Uro, Michael Williams, Sharon Seldon, Jonathan Gay, Robert Tatsumi, Pete Santan- geli, Mark Anders, John Nack, Matt Chotin, AlexHarui, Gordon Smith, Sho Kuwa- moto, Craig Goodman, Stefan Gruenwedel, Deepa Subramaniam, Ethan Malasky, Sean Kranzberg, Michael Morris, Eric Wittman, Jeremy Clark, and Janice Pearce. Table P-1 gives a statistical view of the depth of gratitude I owe this book’s official technical reviewers. Preface | xxxi Thanks to Robyn Thomas, this book’s editor, who reviewed and polished the manu- script with great speed and precision. Thanks also to all of the members of O’Reilly’s management, editorial, production, interior design, art, marketing, and sales teams including Tim O’Reilly, Steve Weiss, and Karen Montgomery. And thanks to the copy editor, Philip Dangler, for helping to ensure the text’s consistency, readability, and accuracy. In addition to being technically reviewed by Adobe staff, this book was also inspected for accuracy and quality by a keen group of beta readers, including Brett Walker, Chafic Kazoun, Derek McKenna, Edwin van Rijkom, Greg Burch, Jim Arm- strong, Jon Williams, Mark Jonkman, Matthew Keefe, Mauro Di Blasi, Ralf Bokel- berg, Ric Ewing, Robin Debreuil, and Victor Allen. The beta readers were indispensable, catching a great number of inconsistencies and subtle code errors. Mark Jonkman bears special mention for his extremely meticulous examination of the manuscript and its code examples. Two mentors who helped shape me as a programmer and a writer are Bruce Epstein and Derek Clayton. Bruce was the editor for all of my previous books, and his rich lessons still inform every word I write. Derek is the creator of moock.org’s Unity multiuser server (http://www.moock.org/unity), and a regular source of programming inspiration and friendship. Table P-1. Adobe reviewers Reviewer Title Chapters reviewed Number of emails fielded Deneb Meketa Computer Scientist, Flash Platform 17 75 Erica Norton Senior Quality Engineer, Flash Player 14, 19, 21, 22 3 Francis Cheng Senior Technical Writer 1-11, 13, 15, 16, 18 334 Jeff Dyer Compiler Architect, ActionScript Language Group 17 106 Jeff Mott Computer Scientist, Flash Player Engineering 12, 20-25 85 Jim Corbett Senior Computer Scientist, Flash Player Engineering 20, 23, 24, 28 52 Lee Thomason Architect, Flash Player 25, 27 33 Mike Chambers Senior Product Manager, Developer Relations, Adobe AIR 189 Mike Richards Computer Scientist, Mobile and Devices 22-26 9 Paul Robertson ActionScript Developer/Writer 1, 2, 24, 27-31 14 Paul Betlem Senior Director, Flash Player Engineering 20, 27, 26 19 Rebecca Sun Computer Scientist, Flash Authoring 7, 29, 31 60 Robert Penner Senior Engineer, Flash Authoring 18, 23-25 16 Roger Gonzalez Flex Compiler Architect 25, 30, 31 64 Werner Sharp Senior Computer Scientist, Flash Player Engineering 18, 22 35 Of course, no book on any ECMAScript-based language is complete without acknowledging Brendan Eich’s pioneering of JavaScript and ongoing development of ECMAScript. Thanks Brendan! Finally, love and peace to the following for their love and friendship: James Porter, Graham Barton, Joe Duong, Tommy Jacobs, Wendy Schaffer, Andrew Harris, Dave Luxton, Dave Komlos, Marco Crawley, Eric Liphardt, Ken Reddick, Mike Linkov- ich, Matt Wearn, Mike Dobell, Mike “Nice,” Hoss Gifford, Erik Natzke, Jared Tar- bell, Marcos Weskamp, Dan Albritton, Francis Bourre, Thijs Triemstra, Veronique Brossier, Saima Khokhar, Amit Pitaru, James Patterson, Joshua Davis, Branden Hall, Robert Hodgin, Shin Matsumura, Yugo Nakamura, Claus Whalers, Darron Schall, Mario Klingeman, Fumio Nonaka, Robert Reinhardt, Grant Skinner, and the Moocks. —Colin Moock March 2007 Toronto, Canada PART I I. ActionScript from the Ground Up Part I provides exhaustive coverage of the core ActionScript 3.0 language, covering object-oriented programming, classes, objects, variables, methods, functions, inherit- ance, datatypes, arrays, events, exceptions, scope, namespaces, and XML. Part I closes with a look at Flash Player’s security architecture. When you complete Part I, you will have gained a deep knowledge of core Action- Script 3.0, and applied that knowledge to the development of a virtual zoo example- application. Chapter 1, Core Concepts Chapter 2, Conditionals and Loops Chapter 3, Instance Methods Revisited Chapter 4, Static Variables and Static Methods Chapter 5, Functions Chapter 6, Inheritance Chapter 7, Compiling and Running a Program Chapter 8, Datatypes and Type Checking Chapter 9, Interfaces Chapter 10, Statements and Operators Chapter 11, Arrays Chapter 12, Events and Event Handling Chapter 13, Exceptions and Error Handling Chapter 14, Garbage Collection Chapter 15, Dynamic ActionScript Chapter 16, Scope Chapter 17, Namespaces Chapter 18, XML and E4X Chapter 19, Flash Player Security Restrictions 3 Chapter 1 CHAPTER 1 Core Concepts2 A program is a set of written instructions to be executed (i.e., carried out) by a com- puter or a software application. The written, human-readable text of a program is called source code, or just code. The person who creates a program is called a pro- grammer,acoder,oradeveloper. Every program is written in a particular program- ming language, just as every book is written in a particular language (English, Russian, Japanese, etc.). Programming languages dictate the syntaxand grammar that programmers must use to form the instructions in a given program. This book provides from-the-ground-up coverage of the syntax, grammar, and usage of one spe- cific programming language, ActionScript 3.0. Get ready for a good time. Tools for Writing ActionScript Code ActionScript code is written in plain text, so an ActionScript program can be created with nothing more than a simple text editor, such as Notepad on Windows or TextEdit on Macintosh. However, most ActionScript programmers write ActionScript code using one (or both) of two commercial tools produced by Adobe Systems Incor- porated: Flex Builder and the Flash authoring tool. FlexBuilder is an integrated development environment,orIDE. An IDE is an applica- tion for writing and managing code, much as a word processor is an application for creating printed documents. Developers use FlexBuilder to create software applica- tions and multimedia content using either ActionScript or MXML, or both. MXML is an XML-based language for describing user interfaces. By contrast, the Flash authoring tool is a hybrid design, animation, and program- ming editor. Developers use the Flash authoring tool to create software applications and multimedia content by combining ActionScript code with manually drawn graphics, animation, and multimedia assets. 4 | Chapter 1: Core Concepts ActionScript 3.0 is supported by FlexBuilder 2 or higher, and Flash CS3 (Version 9 of the Flash authoring tool) or higher. To obtain a copy of FlexBuilder, visit http:// www.adobe.com/products/flex/productinfo/overview/. To obtain a copy of the Flash authoring tool, visit http://www.adobe.com/go/flash/. The vast majority of this book concentrates on the creation of software applications and multimedia content using pure ActionScript (i.e., code only). Chapter 29 covers the use of ActionScript in the Flash authoring tool. This book specifically does not include coverage of MXML. For coverage of MXML, see O’Reilly’s Programming Flex 2 (Kazoun and Lott, 2007) and Adobe’s Flex Builder documentation. Flash Client Runtime Environments ActionScript programs can be executed by three different software applications (all produced by Adobe): Flash Player, Adobe AIR, and Flash Lite. Flash Player executes ActionScript programs in a web browser or in a standalone mode on the desktop. Flash Player has very little access to the operating system (e.g., it cannot manage files, control windows, or access most hardware). Adobe AIR executes ActionScript programs on the desktop and has full integration with the desktop operating system (e.g., can manage files, control windows, and access hardware). Flash Lite executes ActionScript programs on mobile devices, such as cellular phones. As of the publication of this book, Flash Lite can execute ActionScript pro- grams written in ActionScript 2.0, but not ActionScript 3.0, while Flash Player and Adobe AIR can execute programs written in ActionScript 3.0. Therefore, the tech- niques taught in this book apply to Flash Player and Adobe AIR, but will not apply to Flash Lite until it adds support for ActionScript 3.0. In generic terms, Flash Player, Adobe AIR, and Flash Lite are all known as Flash cli- ent runtime environments (or Flash runtimes for short) because they manage Action- Script programs while they execute, or “run.” Flash runtimes are available for Windows, Macintosh, and Linux, as well as a variety of different mobile hardware devices. Because ActionScript programs are executed by a Flash runtime, not a spe- cific operating system or hardware device, each ActionScript program is considered portable because it can run on different hardware devices (phones, game consoles) and operating systems (Windows, Macintosh, and Linux). In casual discussion, the term ActionScript virtual machine is sometimes used as an equivalent for Flash client runtime environment. There is, however, a difference between these two terms, so they should not be used interchangeably. The Action- Script virtual machine (AVM) is technically the software module inside Flash Player, Adobe AIR, and Flash Lite that executes ActionScript programs. But each Flash run- time also has other responsibilities, such as displaying content on screen, playing Compilation | 5 video and audio, and communicating with the operating system. The specific ver- sion of the ActionScript virtual machine that runs ActionScript 3.0 code is known as AVM2. The specific version of the ActionScript virtual machine that executes Action- Script 1.0 and ActionScript 2.0 code (not covered in this book) is known as AVM1. Compilation Before an ActionScript program can be executed by a Flash runtime, it must be con- verted from human-readable ActionScript 3.0 code to a condensed, binary format that Flash runtimes understand, known as ActionScript bytecode,orABC. On its own, however, ActionScript bytecode cannot be executed by Flash runtimes; instead, it must be wrapped in a binary container file known as a .swf file. The .swf file stores the bytecode and any embedded media assets required by the ActionScript program in Flash file format,orSWF. The process of converting an ActionScript program to bytecode is known as compiling the program. The process of generating a .swf file is known as compiling the .swf file, or sometimes, exporting or publishing the .swf file. To compile ActionScript 3.0 programs and .swf files, we use a software module known as a compiler. A compiler that compiles ActionScript code is known as an ActionScript compiler. A compiler that generates .swf files is known as a SWF com- piler. Any SWF compiler that claims full support for the Flash file format includes an ActionScript compiler. Naturally, both FlexBuilder 2 and the Flash authoring tool include a SWF compiler (and, by extension, an ActionScript compiler). Flex Builder 2 and the Flash authoring tool share the same ActionScript compiler but have differ- ent SWF compilers—known, respectively, as the Flex compiler and the Flash com- piler. Adobe also offers the Flexcompiler as a standalone command-line application called mxmlc. The mxmlc compiler is included in Adobe’s free developer’s toolkit, the Flex 2 SDK, available at http://www.adobe.com/go/flex2_sdk. Just-In-Time Compilation When an ActionScript program runs, the Flash runtime reads compiled ActionScript bytecode and translates it into native machine-code instructions that are executed by the specific computer hardware on which the program is running. In many cases, the native machine-code instructions are saved so they can be used again without the need to be retranslated from ActionScript bytecode. Just as converting ActionScript 3.0 code to bytecode is called compiling, the process of translating ActionScript bytecode into native machine code and then saving that machine code for later execution is, likewise, known as compiling. Hence, most ActionScript code undergoes two levels of compilation. First, the developer compiles the code from human-readable format to a format understood by the Flash runtime (ActionScript bytecode). Then, the Flash runtime automatically compiles the Action- Script bytecode to a format understood by the hardware running the program (native 6 | Chapter 1: Core Concepts machine code). The latter form of compilation (bytecode to machine code) is known as just-in-time compilation,orJIT, because it happens immediately before the spe- cific bytecode being compiled is needed by the program. Just-in-time compilation is sometimes also called dynamic translation. Experienced programmers may be inter- ested to know that code at the top level of a class definition is not just-in-time com- piled (because it is executed only once). Quick Review The past several pages covered a lot of ground. Let’s review what we’ve covered so far. An ActionScript program is a set of instructions to be executed by one of the Flash runtimes: Flash Player, Adobe AIR, or Flash Lite. ActionScript programs can be writ- ten in a text editor, Flex Builder, or the Flash authoring tool. In order to run an ActionScript program, we must first compile it into a .swf file using a SWF compiler such as the Flash compiler included with the Flash authoring tool, or mxmlc, which is included with both Flex Builder 2 and the Flex 2 SDK. Don’t worry if some of the preceding concepts or terms are new to you. We’ll be applying them abundantly over the next 900-plus pages. Now let’s write some code! Classes and Objects Imagine you are going to build an airplane, entirely from scratch. Think about the process you would follow. You very likely wouldn’t just head to a metal shop and start welding. You’d have to draw up a blueprint for the airplane first. In fact, given that you are building the airplane from scratch, you’d have to draw up not just one, but many blueprints—one for each of the airplane’s many parts (the wheels, the wings, the seats, the brakes, and so on). Each blueprint would describe a specific part conceptually and correspond to an actual part in the physical incarnation of the airplane. To build the airplane, you would manufacture each of the parts individu- ally, and then assemble them according to a master blueprint. The interoperation of the airplane’s assembled parts would produce the airplane’s behavior. If that all sounds logical to you, you’ve got what it takes to become an ActionScript programmer. Just as an airplane flying through the sky is a group of interoperating parts based on a set of blueprints, a running ActionScript program is a group of inter- operating objects, based on a set of classes. ActionScript objects represent both the tangible things and the intangible concepts in a program. For example, an object might represent a number in a calculation, a clickable button in a user interface, a point in time on a calendar, or a blur effect on an image. Objects are incarnations, or instances, of classes. Classes are the blueprints upon which objects are based. Classes and Objects | 7 The first step in writing a new program is determining its classes. Each class describes, in code, both the characteristics and behavior of a particular type of object. Some of the classes in a program must be written from scratch, while others are provided by ActionScript and the various Flash runtimes. Classes written from scratch (known as custom classes) are used to produce specialized types of content, such as an order form for a shopping application, a car in a racing game, or a mes- sage in a chat application. By contrast, classes provided by ActionScript and the vari- ous Flash runtimes (known as built-in classes) are used to perform fundamental tasks such as creating numbers and text, playing sounds, displaying images, accessing the network, and responding to user input. From the classes in a program, we make (or instantiate) objects and then tell those objects what to do. What the objects do determines the behavior of the program. Building a program with classes and objects is known as object- oriented programming (OOP). In the next section we’ll start writing an actual program, but before we do, let’s take a brief look at an important group of classes, known as native classes, that are built directly into ActionScript. The native classes, listed in Table 1-1, are used to manipu- late basic types of information, such as numbers and text. You can expect to use instances of at least one or two of the native classes in every program you write— much like you might use ready-made parts from a third-party supplier when build- ing an airplane. Read over Table 1-1 for basic familiarity. In the coming chapters, we’ll study the native classes in much more detail. Table 1-1. ActionScript’s native classes Class Description String Represents textual data (i.e., a string of characters) Boolean Represents the logical states true and false Number Represents floating-point numbers (i.e., numbers with a fractional value) int Represents integer numbers (i.e., numbers with no fractional value) uint Represents positive integer numbers Array Represents an ordered list Error Represents a program error (i.e., a problem in your code) Date Represents a specific point in time Math Contains common mathematical values and operations RegExp Defines tools for searching and replacing text Function Represents a reusable set of instructions that can be executed, or called, repeatedly Object Defines the basic features of every object in ActionScript 8 | Chapter 1: Core Concepts Now let’s try using classes and objects in an example program—a simple simulated zoo game with virtual pets. Using the technique known as timeline scripting in the Flash authoring tool, it is possible to create an ActionScript program without first cre- ating a class (see Chapter 29). However, even if you never expect to create classes yourself, you should still study the techniques presented in this chapter. Knowing how classes are created will greatly deepen your understanding of ActionScript and make you a better programmer. Creating a Program As we just learned, ActionScript programs are made up of classes, which are the blueprints for the interoperating parts (objects) of a program. Typically, the develop- ment of a new ActionScript program starts with a design phase, during which the program’s functionality is broken into a logical set of classes. Each class is given a name, a set of features, and a role in the larger program. One class in particular is designated as the main class. The main class provides the starting point, or program point of entry, for the application. To start a new program, the Flash runtime auto- matically creates an instance of the program’s main class. For our virtual zoo example program, we’ll name the main class VirtualZoo. As the first step in building the program, we’ll create a folder on the filesystem, named virtualzoo. Within that folder, we’ll create a subfolder named src (short for source)in which to store all .as files (i.e., all files containing source code). Each program’s main class code must be placed in a text file named after the main class, and given the extension .as. Accordingly, we’ll create an empty text file named VirtualZoo.as. Notice that the filename VirtualZoo.as exactly matches the class name VirtualZoo and that case sensitivity matters. We’ll place VirtualZoo.as in the folder virtualzoo/src. Here’s the file structure for our program’s source files so far: virtualzoo |- src |- VirtualZoo.as With VirtualZoo.as created, we can start writing the VirtualZoo class. However, first we must deal with a potential problem—if our chosen main class name conflicts with (i.e., is the same as) one of ActionScript’s built-in classes, then ActionScript won’t let us create the class, and our program won’t be able to start. To prevent potential naming conflicts in our program, we use packages. There is a lot of ground to cover, so we won’t actually compile our zoo program’s code until Chapter 7. If you decide to jump ahead and com- pile the examples presented in Chapters 1 through 6, you are likely to encounter various warnings and errors. After Chapter 7, you’ll be able to compile all versions of the example program without errors. Packages | 9 Packages Like its name suggests, a package is a conceptual container for a group of classes and, as we’ll learn later, for other things in a program. Each package delimits an independent physical region of a program and gives that region a name, called the package name. By convention, package names typically start with a lowercase letter while class names typically start with an uppercase letter. This helps distinguish package names from class names. When a class’s source code resides within a package, that class automatically adopts the package’s name as part of its own name, much like a child takes on his parents’ family name. For example, a class named Player in a package named game becomes known as game.Player. Notice that the package name comes first and is separated from the class name using a period (.) character (character is simply programming jargon for letter, number, punctuation, and so on). The package name helps distin- guish the game.Player class from other classes also named Player, thus preventing name conflicts between different parts of a program or between a program’s custom classes and ActionScript’s built-in classes. To create a new package, we use a package definition directive. Let’s dissect that term. In ActionScript, all program instructions are known generally as directives. Definitions are one type of directive; they create, or define something, such as a pack- age or a class. In this case, the thing being defined is a package, hence the term, pack- age definition directive. A definition that creates something in a program is said to define or declare that thing. Definitions are sometimes also referred to as declarations. Here’s the general form of a package definition directive: package packageName { } All package definitions start with a keyword: package.Akeyword is a command name reserved for use by the ActionScript language. In this case, the package keyword tells ActionScript to create a package. After the package keyword, we pro- vide the desired package name, represented by packageName in the preceding code. (Throughout this book, italicized code, such as packageName, indicates text that must be replaced by the programmer.) Next, we mark the beginning and end of the pack- age contents using curly braces: { and }. To add a class to a package, we insert its source code between the curly braces, as follows: package packageName { Class source code goes here } 10 | Chapter 1: Core Concepts In technical terms, the curly braces in a package definition are a kind of statement, known as a block statement. Like definitions, statements are a kind of directive, or basic program instruction. A block statement marks the beginning and end of a group of directives that should be treated as a logical whole. A package definition’s block statement is known as a package block or sometimes package body. For a complete list of ActionScript statements, see Chapter 10. By convention (but not necessity), package names typically have the following structure: • The reversed domain name of the organization creating the program • Followed by a period (.) • Followed by the general purpose package’s contents For example, a package containing classes for a mapping application created by Acme Corp., whose domain name is acme.com, might be named com.acme.map,as shown in the following code: package com.acme.map { } Notice that com precedes acme (i.e., in the package name, the domain name is reversed). Domain names are guaranteed to be unique by the system of autho- rized top-level-domain registrars; thus, starting your package names with your organization’s domain name avoids name conflicts with code developed by other organizations. Now let’s try using packages in our virtual zoo program. To keep our example sim- ple, we’ll use the package name zoo, without any leading domain name. To define the zoo package, we’ll add the following code to the file VirtualZoo.as: package zoo { } Now that we’ve added a package to the file VirtualZoo.as, we must change that file’s location on the filesystem to match the package it contains. Due to a require- ment imposed by all Adobe’s ActionScript compilers, when a source file contains a class (or other definition) in a package, it must reside in a folder structure that matches that package name. For example, a file that contains a package named com.gamecompany.zoo must reside in a folder named zoo, in a folder named gamecompany, contained by a folder named com (i.e., com/gamecompany/zoo). Accordingly, we’ll create a new folder named zoo in our program’s file structure Defining a Class | 11 and move VirtualZoo.as into it. The file structure for our program’s source files becomes: virtualzoo |- src |- zoo |- VirtualZoo.as Now that we have a package definition, let’s add the VirtualZoo class to it. Defining a Class To create a new class, we use a class definition, as shown in the following generalized code: class Identifier { } A class definition starts with the keyword class, followed by a class name, repre- sented by Identifier in the preceding code. The term identifier simply refers to a name. Identifiers must not contain spaces or dashes, and cannot start with a num- ber. Class names conventionally use a capital letter for the first, and all subsequent words in the name, as in Date or TextField.(TextField is a built-in Flash-runtime class whose instances represent text that can be displayed on screen.) The curly braces ({}) following Identifier in the preceding class definition are a block statement, just like the block statement in a package definition. A class defini- tion’s block statement is known as the class block or sometimes the class body. The class block contains directives that describe the characteristics and behavior of the class and its instances. Here’s the basic class definition for the main class of our simulated zoo game, VirtualZoo. We place the class definition in the package body, in the file VirtualZoo.as: package zoo { class VirtualZoo { } } Because the preceding VirtualZoo class definition resides in a package named zoo, the complete name of the class (known as the fully qualified class name) is zoo.VirtualZoo. In casual discussion, however, we’ll use the shorter, unqualified class name, VirtualZoo. Now that we have our program’s main class defined, let’s create one of the other classes in the program—VirtualPet. From the VirtualPet class, we’ll create objects representing pets in the zoo. 12 | Chapter 1: Core Concepts Like VirtualZoo, we’ll place the source code for the VirtualPet class in the zoo pack- age, in its own file named VirtualPet.as saved in the zoo folder. Here’s the code from the VirtualPet.as file: package zoo { class VirtualPet { } } Notice that a package definition can span multiple source files. Even though VirtualZoo and VirtualPet are stored in different .as files, they belong to the same package, zoo. Any class in any file that resides in a package named zoo is considered part of the zoo package. By contrast, a class definition cannot span multiple files; it must be written, in its entirety, within a single file. Access Control Modifiers for Classes By default, a class in a given package can be used by code that also resides in that package only. To make a class available for use outside the package in which it is defined, we must define it with the public attribute. In general terms, a class’s attributes dictate how the class and its instances can be used in a program. Attributes are listed before the keyword class in a class definition, as shown in the following generalized code: attribute class ClassIdentifier { } For example, to add the public attribute to the VirtualPet class, we’d use: package zoo { public class VirtualPet { } } However, in the case of VirtualPet, the public attribute is unnecessary because VirtualPet is used by the VirtualZoo class only, and VirtualZoo can use the VirtualPet class because both classes reside in the zoo package (classes in the same package can always access each other). Hence, we can return to our original VirtualPet definition, which implicitly allows VirtualPet to be used within the zoo package only: package zoo { class VirtualPet { } } If we wish to explicitly indicate that we intend VirtualPet to be used within the zoo package only, we can use the internal attribute, as shown in the following code: package zoo { internal class VirtualPet { } } Virtual Zoo Review | 13 A class defined with the internal attribute can be used within its containing package only. That is, defining a class with the internal attribute is identical to defining the class with no access-control modifier at all. The internal attribute simply serves to make the programmer’s intention unambiguous. The internal and public attributes are known as access-control modifiers because they control the region within which a class can be used (accessed) within a program. Unlike the VirtualPet class, the VirtualZoo class must be defined with the public attribute because it is the application’s main class. Adobe’s compilers require an application’s main class to be defined with the public attribute. The following code updates VirtualZoo to include the necessary public attribute: package zoo { public class VirtualZoo { } } Virtual Zoo Review Our game now has two classes: VirtualZoo (the main class) and VirtualPet (which represents the pets in the game). The classes reside in the package zoo, and are stored in plain-text files named VirtualZoo.as and VirtualPet.as, respectively. By require- ment of Adobe’s ActionScript compilers, VirtualZoo is defined with the public attribute because it is the application’s main class. By contrast, VirtualPet is defined with the internal attribute, so it can be used inside the zoo package only. Example 1-1 shows the code for our game so far. The example also introduces some- thing new—code comments. A code comment is a note meant to be read by program- mers only and is completely ignored by the compiler. ActionScript code comments come in two varieties: single line, which start with two slashes (//), and multiline, which start with the character sequence /*, and end with the character sequence */. This is a single-line comment: // No one here but us programmers This is a multiline comment: /* No one here but us programmers */ The current code for our zoo game follows. 14 | Chapter 1: Core Concepts Now let’s carry on with the development of our program, starting with the construc- tor method of our main class, VirtualZoo. Constructor Methods A constructor method (or, constructor, for short) is a discrete set of instructions used to initialize the instances of a class. To create a constructor method, we use a func- tion definition within a class block, as shown in the following generalized code: class SomeClass { function SomeClass ( ) { } } In the preceding code, the keyword function begins the constructor method. Next comes the constructor method name, which must exactly match the class name (case sensitivity matters!). The constructor method name is followed by a pair of parenthe- ses that contain a list of constructor parameters, which we’ll study later. The curly braces ({}) following the parameter list are a block statement, just like the block statements in package and class definitions. A constructor method’s block statement is known as the constructor body. The constructor body contains the directives that initialize instances. Whenever a new instance of SomeClass is created, the directives in the constructor body are executed (sequentially, from top to bottom). Executing the directives in the constructor body is known as executing the constructor or, more casually, running the constructor. Constructor methods are created using the function keyword because they are, technically speaking, a type of function. We’ll study func- tions in Chapter 5. When a class does not define a constructor function explicitly, ActionScript auto- matically provides a default constructor that performs no initialization on new instances of the class. Despite this convenience, as a best practice, always include a constructor, even if it is just an empty one. The empty constructor serves as a formal Example 1-1. Zoo game // Contents of the file VirtualZoo.as package zoo { public class VirtualZoo { } } // Contents of the file VirtualPet.as package zoo { internal class VirtualPet { } } Constructor Methods | 15 indication that the class design does not require a constructor and should be accom- panied by a comment to that effect. For example: class SomeClass { // Empty constructor. This class does not require initialization. function SomeClass ( ) { } } Unlike classes, the accessibility of constructor methods cannot be controlled with access-control modifiers. In ActionScript 3.0, all constructor methods are implicitly considered public. (Future versions of ActionScript might, however, allow for non- public constructor methods.) As a matter of style, this book always includes the public access-control modifier when defining constructor methods, stressing the fact that all constructor methods must be public. The following code demonstrates: class SomeClass { public function SomeClass ( ) { } } The rule that constructor methods must be public in ActionScript 3.0 was instituted due to engineering time constraints and volatility of the ECMAScript 4 Language Specification. For details, see Sho Kuwa- moto’s article at: http://kuwamoto.org/2006/04/05/as3-on-the-lack-of- private-and-protected-constructors. (Sho is Adobe’s FlexBuilder 2’s development team lead.) The constructor method of an application’s main class plays a special role in a pro- gram. It provides an opportunity to execute code immediately after the application has started. As such, the constructor method of an application’s main class is consid- ered the program point of entry. The following code adds a constructor method to our VirtualZoo class (shown in bold): package zoo { public class VirtualZoo { public function VirtualZoo ( ) { } } } Our application now has an official point of entry. When our application starts, the Flash runtime will automatically create a VirtualZoo instance, executing the VirtualZoo constructor method in the process. Given that our application is a virtual zoo, the first thing we’ll do in the VirtualZoo constructor method is create a VirtualPet object (i.e., add a pet to the zoo). We’ll learn how to create objects next. 16 | Chapter 1: Core Concepts Creating Objects To create an object from a class (known technically as instantiating the object), we use the keyword new in combination with the name of the class. The following gen- eralized code shows the approach: new ClassName For example, to make an object from our VirtualPet class, we use the following code: new VirtualPet Multiple independent objects can be made from the same class. For example, the fol- lowing code creates two VirtualPet objects: new VirtualPet new VirtualPet Literal Syntax We’ve just learned that the generalized syntax for creating a new object is: new ClassName That syntaxapplies to both built-in and custom classes. For example,the following code creates a new instance of the built-in Date class, which represents a particular point in time: new Date However, for some native classes, ActionScript also offers an alternative, more con- venient means of creating instances, known as literal syntax. For example, to create a new Number instance representing the floating-point number 25.4, we can use the convenient literal form: 25.4 Likewise, to create a new String instance representing the text “hello,” we can use the convenient literal form: "hello" Finally, to create a new Boolean instance representing the logical state of true,we can use the convenient literal form: true And to create a new Boolean instance representing the logical state of false, we can use the convenient literal form: false Literal syntaxis also available for the Object, Function, RegExp, and XML classes. We’ll study Object literal syntaxin Chapter 15, Function literal syntaxin Chapter 5, and XML literal syntaxin Chapter 18. For information on RegExp literal syntax, see Adobe’s documentation. Creating Objects | 17 Object Creation Example: Adding a Pet to the Zoo Now that we know how to create objects, we can add a VirtualPet object to our zoo program. The following code does just that: package zoo { public class VirtualZoo { public function VirtualZoo ( ) { new VirtualPet } } } Notice that the preceding code refers to the VirtualPet class by its unqualified name, VirtualPet—not by its qualified name, zoo.VirtualPet. Code in a given package can refer to the classes in that package by their unqualified names. By contrast, code in a given package cannot refer to classes in other packages at all. To gain access to a public class in another package, we use the import directive, which has the following general form: import packageName.className; In the preceding code, packageName is the name of the class’s package, and className is the name of the public class we wish to use. If the specified class is not public, the import attempt fails because a non-public class cannot be used outside its package. Once a class has been imported, it can then be referred to by its unqualified name. For example, to create an instance of the built-in flash.media.Sound class (which is used to load and play sounds), we would use the following code: import flash.media.Sound new Sound Importing a class at the package-level makes that class available to code throughout the entire package body. For example, the following code imports flash.media.Sound at the package-level, and then later creates an instance of the Sound class within the VirtualZoo constructor method: package zoo { import flash.media.Sound public class VirtualZoo { public function VirtualZoo ( ) { new Sound } } } If a class’s unqualified name conflicts with the unqualified name of another class, then qualified names must be used to differentiate the two classes. For example, if we were to define a class in the zoo package named Sound, then, within the zoo 18 | Chapter 1: Core Concepts package, we would use the following code to create an instance of the built-in flash.media.Sound class (notice the use of the qualified name): new flash.media.Sound And we would use the following code to create an instance of the zoo package’s Sound class: new zoo.Sound Use of the unqualified class name (e.g., Sound) on its own causes an error that pre- vents the offending program from compiling. Errors that prevent a program from compiling are known as compile-time errors. To gain access to all the public classes in another package, we use the following code: import packageName.* For example, to gain access to all the public classes in the flash.media package, we use the following code: import flash.media.* Note that classes contained by a package that has no name are placed in an automat- ically created package known as the unnamed package. Classes in the unnamed pack- age can be used directly anywhere in a program, without the need for the import directive. In other words: package { // Classes defined here are in the unnamed package, and can be // used directly anywhere in a program } However, as a best practice, you should avoid defining classes in the unnamed pack- age because their names might conflict with classes (and other kinds of definitions) defined by ActionScript, other programs, or even other parts of the same program. On a technical level, the import directive opens the public namespace of the specified package for the current scope and all nested scopes. But if you are new to ActionScript, you needn’t worry about the tech- nical details of the import directive. We’ll examine everything you need to know in the chapters to come. Now let’s return to the task of creating objects in the virtual zoo program. Recall the following code, which creates a new VirtualPet object (shown in bold): package zoo { public class VirtualZoo { public function VirtualZoo ( ) { new VirtualPet } } } Variables and Values | 19 The preceding code successfully creates a new VirtualPet object, but it also suffers from a problem: after the object has been created, the program has no way to refer to it. As a result, our program cannot subsequently use or control the new pet. To give our program a way to refer to the VirtualPet object, we use variables. Variables and Values In ActionScript, every object is considered a single, self-contained piece of data (i.e., information) known as a value. Apart from objects, the only other legal values in ActionScript are the special values null and undefined, which represent the concept of “no value.” A variable is an identifier (i.e., a name) associated with a value. For example, a variable might be the identifier submitBtn associated with an object repre- senting a button in an online form. Or a variable might be the identifier productDescription associated with a String object that describes some product. Variables are used to keep track of information in a program. They give us a means of referring to an object after it is created. Variables come in four varieties: local variables, instance variables, dynamic instance variables, and static variables. We’ll study the first two varieties now, and the remaining two varieties later in this book. Local Variables Local variables are used to track information temporarily within the physical con- fines of a constructor method, an instance method, a static method, or a function. We haven’t studied instance methods, static methods, or functions yet so for now we’ll focus on local variables in constructor methods. To create a local variable within a constructor method, we use a variable definition, as shown in the following generalized code. Notice that the definition starts with the keyword var and, by convention, ends in a semicolon, as do all directives that do not include a block statement. The semicolon indicates the end of the directive, much like the period at the end of a sentence in a natural language. class SomeClass { public function SomeClass ( ) { var identifier = value; } } In the preceding code, identifier is the local variable’s name, and value is the value associated with that variable. Together, the equals sign and the value are known as the variable initializer because they determine the initial value of the variable. 20 | Chapter 1: Core Concepts Associating a variable with a value is known as assigning, setting,or writing the variable’s value. When the variable initializer is omitted, ActionScript automatically assigns the vari- able a default value. Default values for variables are discussed in Chapter 8. A local variable can be used within the method or function that contains its defini- tion only. When the method or function finishes executing, the local variable expires and can no longer be used by the program. Let’s create a local variable to refer to the VirtualPet object we created earlier in the VirtualZoo constructor. We’ll name the local variable pet, and we’ll use an initializer to associate it with the VirtualPet object. Here’s the code: package zoo { public class VirtualZoo { public function VirtualZoo ( ) { var pet = new VirtualPet; } } } Having associated the local variable pet with a VirtualPet object, we can now use that variable to refer to, and therefore control, that object. However, currently our VirtualPet object can’t actually do anything because we haven’t programmed its functionality. We’ll start to rectify that shortcoming in the next section by giving pets the ability to have nicknames. Instance Variables Earlier we learned that a class describes the characteristics and behavior of a particu- lar type of object. In object-oriented programming terms, a “characteristic” is a spe- cific piece of information (i.e., value) that describes some aspect of an object—such as its width, speed, or color. To keep track of an object’s characteristics, we use instance variables. An instance variable is a variable attached to a particular object. Typically, each instance variable describes a characteristic of the object to which it is attached. For example, an instance variable might be the identifier width associated with the value 150, describing the size of the button object in an interface. Or, an instance variable might be the identifier shippingAddress associated with the value “34 Somewhere St,” describing the destination of a product-order object. Instance variables are created using variable definitions directly within class defini- tions, as shown in the following generalized code: class SomeClass { var identifier = value; } Variables and Values | 21 Adding an instance variable definition to a class definition causes that variable to be automatically attached to each instance of the class. As with local variables, the ini- tializer of an instance variable definition specifies the initial value of the instance variable. However, because instance variables are set independently for each individ- ual instance of a class, the initial value of an instance variable is very often omitted and assigned later in the program. As an example, let’s add an instance variable to the VirtualPet class that tracks the nickname of each VirtualPet object. We’ll call our instance variable petName. Here’s the code: package zoo { internal class VirtualPet { var petName = "Unnamed Pet"; } } As a result of the preceding code, the instance variable petName is automatically attached to each new instance of the VirtualPet class. The initial value of petName for all VirtualPet instances is “Unnamed Pet.” However, once each VirtualPet instance is created, a new, custom value can be assigned to its petName variable. To assign an instance variable a new value, we use the following generalized code: object.instanceVariable = value In the preceding code, object is the object whose instance variable will be assigned a value, instanceVariable is one of object’s instance variables (as defined by object’s class), and value is the value to assign. Let’s use the preceding technique to assign a nickname to the VirtualPet object we created earlier in the VirtualZoo constructor. Here’s the code as we last saw it: package zoo { public class VirtualZoo { public function VirtualZoo ( ) { var pet = new VirtualPet; } } } According to the generalized code for assigning an instance variable a new value, we need to start by referring to an object. In this case, we use the local variable pet to refer to the desired VirtualPet instance: pet Next, we write a dot: pet. Then, we write the name of the instance variable whose value we wish to assign—in this case, petName: pet.petName 22 | Chapter 1: Core Concepts Finally, we write an equals sign, then the value we wish to assign to the instance vari- able. Let’s use “Stan”: pet.petName = "Stan" Isn’t that cute? Our pet has a name. We’re making progress. Here’s the code as it appears in our program: package zoo { public class VirtualZoo { public function VirtualZoo ( ) { var pet = new VirtualPet; pet.petName = "Stan"; } } } In the preceding code, notice that the petName instance variable, which is defined in the VirtualPet class, is set through a VirtualPet instance from within the VirtualZoo class. The petName instance variable is, therefore, said to be accessible to code in the VirtualZoo class. When a class makes its instance variables accessible to code in other classes, it is conceptually allowing those classes to modify the characteristics of its instances. The nickname of a pet is a characteristic that naturally lends itself to external modification. However, some instance variables represent characteristics that should not be modified outside the class in which they are defined. For exam- ple, later in this chapter we’ll create an instance variable, caloriesPerSecond, that represents the speed with which a pet digests its food. If an inappropriately small or large value is assigned to caloriesPerSecond, a pet might starve instantly or never grow hungry. Hence, to prevent external code from assigning an inappropriate value to caloriesPerSecond, we must limit access to that variable. To limit access to a vari- able, we use access-control modifiers. Access-control modifiers for instance variables An instance variable’s access-control modifier controls that variable’s accessibility in a program. The access-control modifiers available for instance variable definitions are public, internal, protected, and private. The public and internal modifiers have the same effect with instance variables that they have with classes: an instance variable declared public can be accessed both inside and outside of the package in which it is defined; an instance variable declared internal can be accessed inside the package in which it is defined only. The protected and private modifiers are even more restric- tive than internal. An instance variable declared protected can be accessed by code in the class that contains the variable’s definition, or by code in descendants of that class only (we haven’t studied inheritance yet, so if you are new to object-oriented programming, you can simply ignore protected for now). An instance variable declared private can be accessed by code in the class that contains the variable’s defi- nition only. When no modifier is specified, internal (package-wide access) is used. Variables and Values | 23 Table 1-2 summarizes the access-control modifiers for instance variables. By defining a class’s instance variables as private, we can keep each instance’s infor- mation safely encapsulated, preventing other code from relying too heavily on the internal structure of the class or accidentally assigning invalid values to instance vari- ables. In general, it’s good form to specify an access-control modifier explicitly for every instance variable. No instance variable should be defined as public unless spe- cifically required by its class’s architecture. If you are unsure which access-control modifier to use, use private. Down the road, you can easily make the instance vari- able more accessible if required. By contrast, if you start with a public instance vari- able, you’ll have a tough time changing it to private later if external code already relies on it. In the current version of our virtual zoo application, the petName instance variable is used within both the VirtualPet class and the VirtualZoo class, so we should define petName with the access-control modifier internal, as follows: package zoo { internal class VirtualPet { internal var petName = "Unnamed Pet"; } } Note that defining an instance variable with the internal attribute is identical to defining the variable with no access-control modifier at all (because internal is the default). There are plenty more examples of instance variables throughout the remainder of this book. Now let’s continue with the development of our virtual zoo program. So far, the structure of our VirtualPet class requires each VirtualPet object’s petName variable to be set voluntarily. If, however, we want to guarantee that a name is supplied for every pet, we can use constructor parameters, as described in the next section. Table 1-2. Instance variable access-control modifiers Attribute Code placement Public Internal Protected Private Code in class containing variable’s definition Access allowed Access allowed Access allowed Access allowed Code in descendant of class containing variable’s definition Access allowed Access allowed Access allowed Access denied Codeindifferentclassinsamepackageas variable’s definition Access allowed Access allowed Access denied Access denied Code not in same package as variable’s definition Access allowed Access denied Access denied Access denied 24 | Chapter 1: Core Concepts Constructor Parameters and Arguments A constructor parameter is special type of local variable that is created as part of a constructor-method definition. Unlike regular local variables, a constructor parame- ter’s initial value can be (or in some cases, must be) supplied externally when a new object is instantiated. Constructor parameters are not created with the keyword var. Instead, to create a constructor parameter, we simply provide the desired name and variable initializer within the parentheses of a constructor function definition, as shown in the follow- ing generalized code: class SomeClass { function SomeClass (identifier = value) { } } In the preceding code, identifier is the name of a constructor parameter, and value is the parameter’s initial value. To create more than one parameter for a constructor method, we list multiple parameter names, separated by commas, as shown in the following generalized code (notice the line breaks, which are both legal and common): class SomeClass { function SomeClass (identifier1 = value1, identifier2 = value2, identifier3 = value3) { } } By default, the initial value of a constructor parameter is set to the value supplied in that parameter’s definition. However, a constructor parameter’s value can alterna- tively be supplied when an object is instantiated, using the following generalized object-creation code: new SomeClass(value1, value2, value3) In the preceding code, value1, value2, and value3 are values that are assigned, in order, to the constructor parameters of SomeClass’s constructor method. A value sup- plied to a constructor parameter when an object is instantiated (as shown in the pre- ceding code) is known as a constructor argument. Using a constructor argument to supply the value of a constructor parameter is known as passing that value to the constructor. When a constructor parameter definition does not include a variable initializer, that parameter’s initial value must be supplied via a constructor argument. Such a parame- ter is known as a required constructor parameter. The following generalized code Constructor Parameters and Arguments | 25 shows how to create a class with a single required constructor parameter (notice that the parameter definition does not include a variable initializer): class SomeClass { function SomeClass (requiredParameter) { } } Any code that creates an instance of the preceding class must supply requiredParameter’s value using a constructor argument, as shown in the following generalized code: new SomeClass(value) Failure to supply a constructor argument for a required parameter causes an error either when the program is compiled (if the program is compiled in strict mode) or when the program runs (if the program is compiled in standard mode). We’ll learn the difference between strict mode and standard mode compilation in Chapter 7. When creating a new object without constructor arguments, some pro- grammers choose to retain the constructor-argument parentheses. For example, some programmers prefer to write: new VirtualPet( ) rather than: new VirtualPet The choice is entirely stylistic; ActionScript allows both formats. How- ever, the ActionScript programming community favors the former style (with parentheses) over the latter (without parentheses). Hence, from now on, this book will always include parentheses when creat- ing new objects, even when no constructor arguments are used. Using the preceding generalized parameter code is a guide, let’s add a new construc- tor method to our VirtualPet class, and define a single, required constructor parame- ter, name. We’ll use the value of the name parameter to set each VirtualPet object’s petName instance variable. Here’s the basic code for the constructor method, shown, for the moment, without any code in the constructor body: package zoo { internal class VirtualPet { internal var petName = "Unnamed Pet"; public function VirtualPet (name) { } } } 26 | Chapter 1: Core Concepts Because name is a required parameter, its initial value must be supplied externally at object-creation time. Accordingly, we must update the code that creates our VirtualPet object in the VirtualZoo constructor. Previously, the code looked like this: package zoo { public class VirtualZoo { public function VirtualZoo ( ) { var pet = new VirtualPet; pet.petName = "Stan"; } } } Here’s the updated version, which passes the value “Stan” to the VirtualPet construc- tor instead of assigning it to the new instance’s petName variable: package zoo { public class VirtualZoo { public function VirtualZoo ( ) { var pet = new VirtualPet("Stan"); } } } When the preceding code creates the VirtualPet instance, VirtualPet’s constructor runs, and the constructor argument “Stan” is assigned to the name parameter. Hence, within the VirtualPet constructor, we can use the name parameter to assign the value “Stan” to the new VirtualPet object’s petName instance variable. To do that, we need to specify petName’s value using an identifier expression. The next section describes expressions and identifier expressions. Expressions The written form of a value in an ActionScript program is known as an expression. For example, the following code shows a new expression—an expression represent- ing a new object (in this case, a Date object): new Date( ) Likewise, the following code shows a literal expression representing a Number object with the value 2.5: 2.5 Individual expressions can be combined together with operators to create a com- pound expression, whose value is calculated when the program runs. An operator is a built-in command that combines, manipulates, or transforms values (which are known as the operator’s operands). Each operator is written using either a symbol, such as +, or a keyword, such as instanceof. Expressions | 27 For example, the multiplication operator, which multiplies two numbers, is written using the asterisk symbol (*). The following code shows a compound expression that multiplies 4 and 2.5: 4 * 2.5 When the preceding code is executed, ActionScript calculates the result of multiply- ing 4 by 2.5, and the entire compound expression (4 * 2.5) is replaced by that single calculated result (10). Calculating the value of an expression is known as evaluating the expression. For a complete list of ActionScript operators, see Chapter 10. To represent values that are not known when a program is compiled (at compile- time), but are supplied or calculated when the program runs (i.e., at runtime), we use variable names. When ActionScript evaluates an expression containing a variable name, it replaces that variable name with the corresponding variable’s value. The process of replacing the variable name with the variable’s value is known as retriev- ing, getting, or reading the variable value. For example, consider the following compound expression, in which two values rep- resented by variable names are multiplied together: quantity * price The variables quantity and price are placeholders for values that will be determined at runtime. The value of quantity might be, say, a number supplied by the user, while the value of price might be a number retrieved from a database. For the sake of this example, let’s assume that the variable quantity has the value 2, and the vari- able price has the value 4.99. When ActionScript evaluates the expression quantity * price, it replaces quantity with 2 and price with 4.99. Hence, during evaluation, the expression reads: 2 * 4.99 And the final value of the expression is: 9.98 In formal terms, an expression that contains a variable name only, such as quantity, is known as an identifier expression. Now let’s try using an identifier expression in our virtual pet program. 28 | Chapter 1: Core Concepts Assigning One Variable’s Value to Another When we last saw our virtual zoo program, we had just finished creating a construc- tor method for the VirtualPet class. The constructor method defined a single parame- ter, name, whose value was supplied externally by object-creation code in the VirtualZoo class. Here’s the code for the VirtualPet and VirtualZoo classes, as we left them: // VirtualPet class package zoo { internal class VirtualPet { internal var petName = "Unnamed Pet"; public function VirtualPet (name) { } } } // VirtualZoo class package zoo { public class VirtualZoo { public function VirtualZoo ( ) { var pet = new VirtualPet("Stan"); } } } Now that we know how to use variables in expressions, we can use the name parame- ter to assign the value “Stan” to the new VirtualPet object’s petName instance variable. Recall that to assign an instance variable a new value, we use the following general- ized code: object.instanceVariable = value According to that generalized code, we need to start our variable assignment by referring to an object. In this case, that object is the new VirtualPet instance being created. To refer to it, we use the keyword this, which is an automatically created parameter whose value is the object being created: this Within the body of a constructor method, the object being created is known as the current object. To refer to the current object, we use the keyword this. After the keyword this, we write a dot, followed by the name of the instance vari- able whose value we wish to assign—in this case petName. this.petName Assigning One Variable’s Value to Another | 29 Finally, we write an equals sign, then the value we wish to assign to the instance variable: this.petName = value The value we wish to assign is the value associated with the name parameter. Hence, for value, we write simply: name. this.petName = name At runtime, ActionScript replaces name, in the preceding code, with the value passed to the VirtualPet constructor. That value is then assigned to the instance variable petName. Here’s the assignment code as it appears in our VirtualPet constructor: package zoo { internal class VirtualPet { internal var petName = "Unnamed Pet"; public function VirtualPet (name) { this.petName = name; } } } Now that petName’s value is assigned in the VirtualPet constructor, we can remove the redundant initial value “Unnamed Pet” in the petName variable definition. The petName variable definition used to look like this: internal var petName = "Unnamed Pet"; From now on, it will look like this (notice the removal of the variable initializer): package zoo { internal class VirtualPet { internal var petName; public function VirtualPet (name) { this.petName = name; } } } An expression that assigns a variable a value, such as this.petName = name is known as an assignment expression. The equals sign in assign- ment expressions is an operator called the assignment operator. Copies and References In the preceding section, we learned how to assign one variable’s value to another. Specifically, we assigned the value of the parameter name to the instance variable petName. Here’s the code: this.petName = name; 30 | Chapter 1: Core Concepts The result of assigning the one variable’s value to another variable depends on the type of value being assigned. In an assignment where the source variable’s value is an instance of String, Boolean, Number, int,oruint, ActionScript makes a copy of that value and assigns the copy to the destination variable. After the assignment, two separate copies of the original value exist in system memory—the original value itself, and the copy of that value. The source variable points, or refers, to the original value in memory. The destina- tion variable refers to the new value in memory. By contrast, in an assignment where the source variable’s value is an instance of a custom class or an instance of a built-in class other than String, Boolean, Number, int,oruint, ActionScript associates the second variable directly with the first vari- able’s value. After the assignment, only one copy of the value exists in memory, and both variables refer to it. The variables are said to share a reference to the single object in memory. As a natural consequence, changes to the object made through the first variable are reflected by the second variable. For example, consider the follow- ing code, which creates two local variables, a and b, and then assigns a’s value to b: var a = new VirtualPet("Stan"); var b = a; When the first line of the preceding code runs, ActionScript creates a new VirtualPet object, stores that object in memory, and then associates the local variable a with that object. When the second line of the preceding code runs, ActionScript associ- ates the local variable b with the VirtualPet object already referred to by a. Changes made to the VirtualPet object through a are, hence, naturally reflected by b, and vice versa. For example, if we assign petName using the code b.petName = "Tom", then sub- sequently retrieving a.petName also yields “Tom.” Or, if we assign petName using the code a.petName = "Ken", then subsequently retrieving b.petName also yields “Ken.” A variable associated with an object does not store or contain that object—it simply refers to that object. The object, itself, is stored internally by ActionScript, in system memory. An Instance Variable for Our Pet Earlier, we learned that a local variable expires when the method or function in which it is defined finishes executing. To make sure that the VirtualPet instance in our VirtualZoo class will be accessible after the VirtualZoo constructor finishes, let’s update the VirtualZoo class. Instead of assigning our VirtualPet object to a local vari- able, we’ll assign it to an instance variable, pet. We’ll make pet private so that it can be accessed by code in the VirtualZoo class only. Here’s the code (the new instance variable is shown in bold): package zoo { public class VirtualZoo { private var pet; Instance Methods | 31 public function VirtualZoo ( ) { this.pet = new VirtualPet("Stan"); } } } Over the preceding several sections, we’ve learned how to use instance variables to give characteristics to the objects of a class. Now let’s explore how to use instance methods to give behaviors to the objects of a class. Instance Methods An instance method is a discrete set of instructions that carry out some task related to a given object. Conceptually, instance methods define the things an object can do. For example, the built-in Sound class (whose instances represent sounds in a pro- gram) defines an instance method named play that can start a sound playing. Like- wise, the built-in TextField class (whose instances represent onscreen text) defines a method named setSelection that can change the amount of text selected in the text field. To create an instance method, we use a function definition within a class block, as shown in the following generalized code: class SomeClass { function identifier ( ) { } } In the preceding code, the keyword function begins the instance method. Next comes the instance method name, which can be any legal identifier. (Recall that identifiers must not contain spaces or dashes, and cannot start with a number.) The method name is followed by a pair of parentheses that contain a list of method parameters, which we’ll study later. The curly braces ({}) following the parameter list are a block statement. A instance method’s block statement is known as the method body. The method body contains directives that perform some task. Instance methods are created using the function keyword because they are, technically speaking, a type of function. We’ll study functions in Chapter 5. To execute the code in a given method body, we use a call expression, as shown in the following generalized code. Notice the important and mandatory use of the parentheses operator, (), following the method name. object.methodName() In the preceding code, methodName is the name of the method whose code should be executed, and object is a reference to the specific instance that will conceptually perform the task represented by the specified method. Using a call expression to 32 | Chapter 1: Core Concepts execute the code in an instance method’s body is known as calling a method of an object (or, synonymously calling a method through an object,orcalling an object’s method). The term invoke is also used to mean call. When discussing a particular method by name, most documentation includes the parentheses operator, (). For example, typical documen- tation would write setSelection( ) rather than setSelection. The conven- tion of including the parentheses operator helps distinguish method names from variable names in prose. To further emphasize the distinc- tion between variable names and method names, this book italicizes method names and uses constant-width font for variable names. Let’s put the preceding concepts into practice in our virtual zoo program. To give our pets the ability to eat, we’ll add a new instance variable and a new instance method to the VirtualPet class. The new instance variable, currentCalories, will track the amount of food each pet has eaten, as a numeric value. The new instance method, eat( ), will implement the concept of eating by adding 100 calories to currentCalories. Eventually, the eat( ) method will be called in response to a user action—feeding a pet. The following code shows the currentCalories variable definition. To prevent exter- nal code from tampering with the amount of calories each VirtualPet instance has, we define currentCalories as private. Notice that each new VirtualPet instance is given 1,000 calories to start: package zoo { internal class VirtualPet { internal var petName; private var currentCalories = 1000; public function VirtualPet (name) { this.petName = name; } } } The following code shows the basic eat( ) method definition. Notice that, by conven- tion, instance methods are listed after the class’s constructor method, while instance variables are listed before the class’s constructor method. package zoo { internal class VirtualPet { internal var petName; private var currentCalories = 1000; public function VirtualPet (name) { this.petName = name; } Instance Methods | 33 function eat ( ) { } } } Even though the eat( ) method body does not yet contain any code, with the preced- ing definition in place, we can already invoke the eat( ) method on a VirtualPet object, as shown in the following updated version of the VirtualZoo class: package zoo { public class VirtualZoo { private var pet; public function VirtualZoo ( ) { this.pet = new VirtualPet("Stan"); // Invoke eat( ) on the VirtualPet object referenced by the // variable pet this.pet.eat( ); } } } Within the eat( ) method body, we want to add 100 to the currentCalories variable of the object through which the eat( ) method was called. To refer to that object, we use the keyword this. Within the body of an instance method, the object through which the method is called is known as the current object. To refer to the current object, we use the keyword this. Notice that the term “current object” can refer to either the object being created in a constructor method or the object through which an instance method was called. Adding a numeric value (such as 100) to an existing variable (such as currentCalories) is a two-step process. First, we calculate the sum of the variable and the numeric value; then we assign that sum to the variable. Here’s the general- ized code: someVariable = someVariable + numericValue In the case of the eat( ) method, we want to add 100 to the currentCalories variable of the current object (this). Hence, the code is: this.currentCalories = this.currentCalories + 100; As a convenient alternative to the preceding code, ActionScript offers the addition assignment operator, +=, which, when used with numbers, adds the value on the right to the variable on the left, as shown in the following code: this.currentCalories += 100; Here’s the code as it appears in the VirtualPet class: package zoo { internal class VirtualPet { 34 | Chapter 1: Core Concepts internal var petName; private var currentCalories = 1000; public function VirtualPet (name) { this.petName = name; } function eat ( ) { this.currentCalories += 100; } } } From now on, every time a VirtualPet instance’s eat( ) method is called, that instance’s currentCalories variable will increase by 100. For example, the following code, repeated from the VirtualZoo constructor, increases pet’s currentCalories to 1,100 (because all VirtualPet instances start with 1,000 calories). this.pet = new VirtualPet("Stan"); this.pet.eat( ); Notice that even though the VirtualPet characteristic currentCalories is kept pri- vate, it can still be modified as result of a VirtualPet instance performing a behavior (eating) that is instigated by an external code. In some cases, however, even instance methods must be kept private. As with instance variables, we use access-control modifiers to control the accessibility of instance methods in a program. Access Control Modifiers for Instance Methods The access-control modifiers available for instance method definitions are identical to those available for instance variables—public, internal, protected, and private.An instance method declared public can be accessed both inside and outside of the pack- age in which it is defined; an instance method declared internal can be accessed only inside the package in which it is defined. An instance method declared protected can be accessed by code in the class that contains the method’s definition, or by code in descendants of that class only (we haven’t studied inheritance yet, so if you are new to object-oriented programming, you can simply ignore protected for now). An instance method declared private can be accessed by code in the class that contains the method’s definition only. When no modifier is specified, internal (package-wide access) is used. By adding access-control modifiers to the methods of the class, we can put the “black box” principle into strict practice. In object-oriented programming, each object can be thought of as a black boxthat is controlled by an externalassortment of meta- phoric knobs. The object’s internal operations are unknown (and unimportant) to the person using those knobs; all that matters is that the object performs the desired action. An object’s public instance methods are the knobs by which any programmer can tell that object to perform some operation. An object’s non-public methods Instance Methods | 35 perform other internal operations. Hence, the only methods a class should make publicly accessible are those that external code needs when instructing instances of that class to do something. Methods needed to carry out internal operations should be defined as private, protected,orinternal. As an analogy, think of an object as a car, whose driver is the programmer using the object, and whose manufacturer is the pro- grammer that created the object’s class. To drive the car, the driver doesn’t need to know how a car’s engine works. The driver simply uses the gas pedal to accelerate and the steering wheel to turn. Accelerating the car in response to the driver step- ping on the gas pedal is the manufacturer’s concern, not the driver’s. As you manufacture your own classes, focus as much energy designing the way the class is used as you do implementing how it works internally. Remember to put yourself in the “driver’s seat” regularly. Ideally, the way the class’s public methods are used externally should change very little or not at all each time you make an internal change to the class. If you put a new engine in the car, the driver should still be able to use the gas pedal. As much as possible, keep the volatility of your classes behind the scenes, in private methods. In object-oriented terms, a class’s public instance methods and public instance variables are, together, sometimes called the class’s interface to the outside world—or, synonymously, the class’s API (Application Programming Interface). The term API also refers to the collective services provided by an entire group of classes. For example, the built-in Flash-runtime classes for displaying content on screen are known as the display API. Likewise, a custom set of classes used to render 3D content might be known as a “3D API”. In addition to classes, APIs can also include other program definitions (such as variables and functions). In ActionScript, the term interface has an additional technical mean- ing, covered in Chapter 9. To avoid confusion, this book does not use the term “interface” to describe an object’s public instance methods and public instance variables. Returning to our virtual zoo program, let’s now add an access-control modifier to the VirtualPet class’s eat( ) method. We’ll make eat( ) a public method because it is one of the official means by which external code is intended to control VirtualPet objects. Here’s the revised code: package zoo { internal class VirtualPet { internal var petName; private var currentCalories = 1000; public function VirtualPet (name) { this.petName = name; } public function eat ( ) { this.currentCalories += 100; 36 | Chapter 1: Core Concepts } } } As it stands, the VirtualPet class’s eat( ) method is inflexible because it adds the same amount of calories to currentCalories every time it is called. Eventually, we’ll want to dynamically adjust the amount of calories added when a pet eats based on the type of food fed to it by the user. To allow the amount of calories added at feeding time to be specified externally when eat( ) is called, we need method parameters. Method Parameters and Arguments Like constructor parameters, a method parameter is special type of local variable that is created as part of a method definition, but whose initial value can be (or, in some cases, must be) supplied externally when the method is called. To define a method parameter, we use the following generalized code. Notice that a method-parameter definition has the same general structure as a constructor-param- eter definition. function methodName (identifier1 = value1, identifier2 = value2, ... identifiern = valuen) { } In the preceding code, identifier1=value1,identifier2=value2,... identifiern=valuen is a list of method parameter names and their corresponding ini- tial values. By default, a method parameter’s initial value is the value supplied in that parameter’s definition. However, a method parameter’s value can alternatively be supplied via a call expression, as shown in the following generalized code: theMethod(value1, value2,...valuen) In the preceding code, theMethod is a reference to the method being invoked, and value1, value2,...valuen is a list of values that are assigned, in order, to theMethod’s parameters. A value supplied to a method parameter through a call expression (as shown in the preceding code) is known as a method argument. Using a method argument to supply the value of a method parameter is known as passing that value to the method. As with constructor parameters, when a method parameter definition does not include a variable initializer, that parameter’s initial value must be supplied via a method argument. Such a parameter is known as a required method parameter. The following generalized code shows how to create a method with a single required method parameter (notice that the parameter definition does not include a variable initializer): function methodName (requiredParameter) { } Instance Methods | 37 Any code that calls the preceding method must supply requiredParameter’s value using a method argument, as shown in the following generalized code: theMethod(value) Failure to supply a constructor argument for a required parameter causes an error either when the program is compiled (if the program is compiled in strict mode) or when the program runs (if the program is compiled in standard mode). Now let’s update the VirtualPet class’s eat( ) method to include a required parame- ter, numberOfCalories. Each time eat( ) is called, we’ll increase the value of the cur- rent object’s currentCalories variable by the value of numberOfCalories. Here’s the updated code for the eat( ) method: package zoo { internal class VirtualPet { internal var petName; private var currentCalories = 1000; public function VirtualPet (name) { this.petName = name; } public function eat (numberOfCalories) { this.currentCalories += numberOfCalories; } } } Because numberOfCalories is a required parameter, its initial value must be supplied externally when eat( ) is called. Let’s try it out with the VirtualPet object created in the VirtualZoo constructor. Previously, the code for the VirtualZoo constructor looked like this: package zoo { public class VirtualZoo { private var pet; public function VirtualZoo ( ) { this.pet = new VirtualPet("Stan"); this.pet.eat( ); } } } Here’s the updated version, which passes the value 50 to eat( ): package zoo { public class VirtualZoo { private var pet; 38 | Chapter 1: Core Concepts public function VirtualZoo ( ) { this.pet = new VirtualPet("Stan"); this.pet.eat(50); } } } The preceding call expression causes eat( ) to run with the value 50 assigned to the numberOfCalories parameter. As a result, 50 is added to the currentCalories instance variable of the VirtualPet instance referenced by pet. After the code completes, the value of pet’s currentCalories variable is 1050. Method Return Values Just as methods can accept values in the form of arguments, methods can also pro- duce or return values. To return a value from a method, we use a return statement,as shown in the following general code: function methodName ( ) { return value; } The value returned by a method is known as the method’s return value or result. When a method executes, its return value becomes the value of the call expression that called it. To demonstrate the use of method return values, let’s add a new method to the VirtualPet class that calculates and then returns the age of a pet. In order to be able to calculate a pet’s age, we need a little knowledge of the Date class, whose instances represent specific points in time. To create a new Date instance, we use the follow- ing code: new Date( ) Times represented by Date instances are expressed as the “number of milliseconds before or after midnight of January 1, 1970.” For example, the time “one second after midnight January 1, 1970” is expressed by the number 1000. Likewise, the time “midnight January 2, 1970” is expressed by the number 86400000 (one day is 1000 milliseconds × 60 seconds × 60 minutes × 24 hours). By default, a new Date object represents the current time on the local system. To access a given Date instance’s numeric “milliseconds-from-1970” value, we use the instance variable time. For example, the following code creates a new Date instance and then retrieves the value of its time variable: new Date( ).time; Instance Methods | 39 On January 24, 2007, at 5:20 p.m., the preceding code yielded the value: 1169677183875, which is the precise number of milliseconds between midnight January 1, 1970 and 5:20 p.m. on January 24, 2007. Now let’s return to the VirtualPet class. To be able to calculate the age of VirtualPet objects, we must record the current time when each VirtualPet object is created. To record each VirtualPet object’s creation time, we create an instance of the built-in Date class within the VirtualPet constructor, and then assign that instance to a VirtualPet instance variable, creationTime. Here’s the code: package zoo { internal class VirtualPet { internal var petName; private var currentCalories = 1000; private var creationTime; public function VirtualPet (name) { this.creationTime = new Date( ); this.petName = name; } public function eat (numberOfCalories) { this.currentCalories += numberOfCalories; } } } Using creationTime, we can calculate any VirtualPet object’s age by subtracting the object’s creation time from the current time. We’ll perform that calculation in a new method named getAge( ). Here’s the code: public function getAge ( ) { var currentTime = new Date( ); var age = currentTime.time - this.creationTime.time; } To return the calculated age, we use the following return statement: public function getAge ( ) { var currentTime = new Date( ); var age = currentTime.time - this.creationTime.time; return age; } The following code shows the getAge( ) method in the context of the VirtualPet class: package zoo { internal class VirtualPet { internal var petName; private var currentCalories = 1000; private var creationTime; 40 | Chapter 1: Core Concepts public function VirtualPet (name) { this.creationTime = new Date( ); this.petName = name; } public function eat (numberOfCalories) { this.currentCalories += numberOfCalories; } public function getAge ( ) { var currentTime = new Date( ); var age = currentTime.time - this.creationTime.time; return age; } } } Now let’s use getAge( )’s return value in the VirtualZoo class. Consider the getAge( ) call expression in the following updated version of VirtualZoo: package zoo { public class VirtualZoo { private var pet; public function VirtualZoo ( ) { this.pet = new VirtualPet("Stan"); this.pet.getAge( ); } } } In the preceding code, the expression pet.getAge( ) has a numeric value representing the number of milliseconds since the creation of the VirtualPet object referenced by pet. In order to be able to access that value later in the program, we could assign it to a variable, as follows: package zoo { public class VirtualZoo { private var pet; public function VirtualZoo ( ) { this.pet = new VirtualPet("Stan"); var age = this.pet.getAge( ); } } } Alternatively, in a more complete version of the virtual zoo program, we might dis- play the returned age on screen for the user to see. Method return values are a highly common part of object-oriented programming. We’ll use them extensively throughout this book, as you will in your own code. Instance Methods | 41 Note that like any other expression, a call expression can be combined with other expressions using operators. For example, the following code uses the division oper- ator to calculate half the age of a pet: pet.getAge( ) / 2 Likewise, the following code creates two VirtualPet objects, adds their ages together, and assigns the sum to a local variable, totalAge: package zoo { public class VirtualZoo { private var pet1; private var pet2; public function VirtualZoo ( ) { this.pet1 = new VirtualPet("Sarah"); this.pet2 = new VirtualPet("Lois"); var totalAge = this.pet1.getAge() + this.pet2.getAge( ); } } } Note that when a return statement does not include any value to return, it simply ter- minates the currently executing method. For example: public function someMethod ( ) { // Code here (before the return statement) will be executed return; // Code here (after the return statement) will not be executed } The value of a call expression that calls a method with no return value (or with no return statement at all) is the special value undefined. Return statements with no return value are typically used to terminate methods based on some condition. Method Signatures In documentation and discussions of object-oriented programming, a method’s name and parameter list are sometimes referred to as the method’s signature.In ActionScript, a method signature also includes each parameter’s datatype and the method’s return type. Parameter datatypes and method return types are discussed in Chapter 8. For example, the signature of the eat( ) method is: eat(numberOfCalories) The signature of the getAge( ) method is simply: getAge( ) We’ve now covered the basics of instance methods. Before we conclude this chapter, we’ll study one last issue related to ActionScript vocabulary. 42 | Chapter 1: Core Concepts Members and Properties In the ActionScript 3.0 specification, an object’s variables and methods are referred to collectively as its properties, where property means “a name associated with a value or method.” Confusingly, in other ActionScript documentation (most notably Adobe’s ActionScript Language Reference), the term property is also used to mean “instance variable.” To avoid the confusion caused by this contradiction, this book avoids the use of the term “property” entirely. Where necessary, this book uses the traditional object-oriented programming term instance members (or simply members) to refer to a class’s instance methods and instance variables collectively. For example, we might say “radius is not a member of Box,” meaning that the Box class does not define any methods or variables named radius. Virtual Zoo Review This chapter has introduced a large number of concepts and terms. Let’s practice using them by reviewing our virtual zoo program for the last time in this chapter. Our virtual zoo game has two classes: VirtualZoo (the main class) and VirtualPet (which represents the pets in the zoo). When our program starts, the Flash runtime automatically creates an instance of VirtualZoo (because VirtualZoo is the application’s main class). The act of creating the VirtualZoo instance causes the VirtualZoo constructor method to execute. The VirtualZoo constructor method creates an instance of the VirtualPet class, with a sin- gle constructor argument, “Stan.” The VirtualPet class defines three instance variables, petName, currentCalories, and creationTime. Those three instance variables represent the following pet characteris- tics: the pet’s nickname, the amount of food in the pet’s stomach, and the pet’s birth date. For a new VirtualPet object, the initial value of currentCalories is a number created using the literal expression 1000. The initial value of creationTime is a Date object representing the time at which each VirtualPet object is created. When a VirtualPet object is created, petName is assigned the value of the required constructor parameter, name. The constructor parameter name receives its value through a con- structor argument, supplied by the new expression that creates the VirtualPet object. The VirtualPet class defines two instance methods, eat( ) and getAge( ). The eat( ) method increases currentCalories by the specified numeric value. The getAge( ) method calculates and returns the pet’s current age, in milliseconds. Example 1-2 displays the current code for our zoo program. Break Time! | 43 Break Time! We’ve made great progress in this chapter. There’s lots more to learn, but it’s time for a well-deserved break. When you’re ready for more ActionScript 3.0 essentials, head on to the next chapter. Example 1-2. Zoo program // VirtualPet class package zoo { internal class VirtualPet { internal var petName; private var currentCalories = 1000; private var creationTime; public function VirtualPet (name) { this.creationTime = new Date( ); this.petName = name; } public function eat (numberOfCalories) { this.currentCalories += numberOfCalories; } public function getAge ( ) { var currentTime = new Date( ); var age = currentTime.time - this.creationTime.time; return age; } } } // VirtualZoo class package zoo { public class VirtualZoo { private var pet; public function VirtualZoo ( ) { this.pet = new VirtualPet("Stan"); } } } 44 Chapter 2CHAPTER 2 Conditionals and Loops 3 In this chapter, we’ll depart from the general topics of classes and objects. Instead, we’ll focus, on two essential types of statements: conditionals and loops. Condition- als are used to add logic to a program, while loops are used to perform repetitive tasks. Both conditionals and loops are extremely common, and can be found in nearly every ActionScript program. Once we’ve finished with conditionals and loops, we’ll return to classes and objects, and continue developing our virtual zoo program. This chapter presents all code examples outside the context of a functioning class or program. However, in a real program, conditionals and loops can be used within instance methods, constructor methods, static methods, functions, directly within class bodies or package bodies, and even outside package bodies. Conditionals A conditional is a type of statement that executes only when a specified condition is met. Conditionals let a program choose between multiple possible courses of action based on the current circumstances. ActionScript provides two different conditionals: the if statement and the switch statement. ActionScript also provides a single conditional operator, ?:, which is cov- ered briefly in Chapter 10. For details on the ?: operator, see Adobe’s ActionScript language reference. The if Statement The if statement is like a two-pronged fork in the road. It contains two blocks of code and an expression (known as the test expression) that governs which block should execute. To create an if statement, we use the following generalized code: if (testExpression) { codeBlock1 } else { Conditionals | 45 codeBlock2 } When ActionScript encounters an if statement, it executes either codeBlock1 or codeBlock2, depending on the value of testExpression. If the value of testExpression is the Boolean value true, then the first block is executed. If the value of testExpression is the Boolean value false, then the second block is executed. If the value of testExpression is not a Boolean value, ActionScript automatically converts testExpression to a Boolean object and uses the result of that conversion to decide which block to execute. (The rules for converting a value to the Boolean class are described in Table 8-5 in Chapter 8.) For example, in the following if statement, the supplied test expression is the Boolean value true, so the value of the variable greeting is set to “Hello”, not “Bonjour”. var greeting; // Test expression is true, so... if (true) { // ...this code runs greeting = "Hello"; } else { // This code doesn't run greeting = "Bonjour"; } Of course, the preceding test expression would rarely, if ever, be used in a real pro- gram because it always produces the same result. In the vast majority of cases, the test expression’s value is dynamically determined at runtime based on information calculated by the program or provided by the user. For example, suppose we’re building a social activity web site that includes gam- bling activities. To participate in a gambling activity, the user must be at least 18 years old. At login time, each user’s status is loaded from a database. The loaded sta- tus is assigned to a variable, gamblingAuthorized.AgamblingAuthorized of true indi- cates that the user is 18 or older; a gamblingAuthorized of false, indicates that the user is under 18. When the user attempts to start gambling, the application uses the following condi- tional statement to determine whether the attempt should be permitted or denied: if (gamblingAuthorized) { // Code here would display the gambling activity's interface } else { // Code here would display an "entry denied" message } Very often, the test expression in an if statement is either an equality expression or a relational expression. Equality expressions and relational expressions use equality operators and relational operators to compare two values, and express the result of 46 | Chapter 2: Conditionals and Loops that comparison as a Boolean value (i.e., either true or false). For example, the following equality expression uses the equality operator (==) to compare expression “Mike” to the expression “Margaret”: "Mike" == "Margaret" The preceding expression evaluates to the Boolean value false because “Mike” is considered not equal to “Margaret”. Likewise, the following relational expression uses the less than operator (<) to com- pare the expression 6 to the expression 7: 6 < 7 This expression evaluates to the Boolean value true because 6 is less than 7. As the preceding examples show, instances of the String class are compared based on the individual characters they represent, and instances of the Number, int, and uint classes are compared based on the mathematical quantities they represent. Note that string comparisons are case-sensitive; for example, “a” is considered not equal to “A”. For the rules governing whether one value is equal to, greater than, or less than another value, see the entries for the ==, ===, <, and > operators in Adobe’s Action- Script Language Reference. Now let’s take a look at an example of an if statement that uses an equality expres- sion as its test expression. Suppose we’re building an online shopping program with a virtual shopping basket. The program maintains an instance variable, numItems, whose value indicates the number of items currently in the user’s shopping basket. When the basket is empty, the program displays the message “Your basket is empty”. When the basket is not empty, the program instead displays the message “Total items in your basket: n” (where n represents the number of items in the basket). The following code shows how our program might create the shopping cart status message. It assigns the value of the variable basketStatus based on the value of numItems. var basketStatus; if (numItems == 0) { basketStatus = "Your basket is empty"; } else { basketStatus = "Total items in your basket: " + numItems; } In the preceding code, if numItems is equal to zero, the program sets basketStatus to the expression: "Your basket is empty" Otherwise, if numItems is greater than zero, the program sets basketStatus to the expression: Conditionals | 47 "Total items in your basket: " + numItems Notice the use of the concatenation operator (+) in the preceding expression. The con- catenation operator converts the numeric value referenced by numItems to a string and then combines that string with the string "Total items in your basket:". The result- ing value is a combination of the two expressions. For example, if numItems is 2, then the result of the concatenation expression is the following string: "Total items in your basket: 2" An if statement with no else When the else clause of an if statement is not needed, it can simply be omitted. For example, suppose that, in our shopping application, if the user orders more than 10 items, the total order is discounted by 10%. At checkout time, we might use code such as the following when calculating the cost of the entire order: if (numItems > 10) { totalPrice = totalPrice * .9; } If numItems is less than 11, totalPrice is simply not altered. Chaining if statements To make a decision between more than two possible courses of action, we chain multiple if statements together, as shown in the following generalized code for a con- dition with three possible outcomes: if (testExpression1) { codeBlock1 } else if (testExpression2) { codeBlock2 } else { codeBlock3 } For example, suppose we’re writing a multilingual application that displays a greet- ing message to its users in one of four languages: English, Japanese, French, or Ger- man. When the program starts, we ask the user to choose a language, and we set a corresponding variable, language, to one of the following strings: “english”, “japa- nese”, “french”, or “german” (notice that the language names are not capitalized; it’s typical to use all lowercase or all UPPERCASE when comparing strings). To create the appropriate greeting message, we use the following code: var greeting; if (language == "english") { greeting = "Hello"; } else if (language == "japanese") { greeting = "Konnichiwa"; } else if (language == "french") { 48 | Chapter 2: Conditionals and Loops greeting = "Bonjour"; } else if (language == "german") { greeting = "Guten tag"; } else { // Code here (not shown) would display an error message indicating // that the language was not set properly } When the preceding code runs, if language’s value is “english”, then greeting is set to “Hello”. If language’s value is “japanese”, “french”, or “german”, then greeting is set to “Konnichiwa”, “Bonjour”, or “Guten tag”, respectively. If language’s value is not “english”, “japanese”, “french”, or “german” (probably due to some program error), then the code in the final else clause is executed. Now that we’re familiar with the if statement, let’s consider the switch statement, which is offered by ActionScript as a convenient way to create a condition with mul- tiple possible outcomes. The behavior of a switch statement can also be implemented with if statements, but switch is considered more legible than if when work- ing with conditions that have multiple possible outcomes. The switch Statement The switch statement lets us execute one of several possible code blocks based on the value of a single test expression. The general form of the switch statement is: switch (testExpression) { case expression1: codeBlock1 break; case expression2: codeBlock2 break; default: codeBlock3 } In the preceding code, testExpression is an expression that ActionScript will attempt to match with each of the supplied case expressions, from top to bottom. The case expressions are supplied with the statement label case, followed by a colon. If testExpression matches a case expression, all statements immediately following that case label are executed—including those in any subsequent case blocks! To prevent subsequent case blocks from executing, we must use the break statement at the end of each block. Alternatively, when we want more than one condition to trigger the execution of the same block of code, we can omit the break statement. For example, in the following code, codeBlock1 executes when testExpression matches either expression1 or expression2: switch (testExpression) { Conditionals | 49 case expression1: case expression2: codeBlock1 break; case expression3: codeBlock2 break; default: codeBlock3 } If no case expression matches testExpression, all statements following the default label are executed. Though the default label is normally listed last, it can legally come anywhere within the switch statement. Furthermore, the default label is not mandatory in a switch statement. If no default is provided and testExpression does not match a case expres- sion, execution flow simply continues after the end of the switch statement block (that is, the code within the switch statement is skipped). The following code shows how to implement the preceding section’s multilingual greeting condition using a switch statement instead of a chain of if statements. Both approaches implement the same behavior, but the switch code is arguably easier to read and scan quickly. var greeting; switch (language) { case "english": greeting = "Hello"; break; case "japanese": greeting = "Konnichiwa"; break; case "french": greeting = "Bonjour"; break; case "german": greeting = "Guten tag"; break; default: // Code here (not shown) would display an error message indicating // that the language was not set properly } 50 | Chapter 2: Conditionals and Loops The switch statement implicitly uses the strict equality operator (===)—not the equality operator (==)—when comparing the testExpression with case expressions. For a description of the differ- ence, see Adobe’s ActionScript Language Reference. Loops In the preceding section, we saw that a conditional causes a statement block to exe- cute once if the value of its test expression is true.Aloop, on the other hand, causes a statement block to be executed repeatedly, for as long as its test expression remains true. ActionScript provides five different types of loops: while, do-while, for, for-in, and for-each-in. The first three types have very similar effects but with varying syntax. The remaining two types are used to access the dynamic instance variables of an object. We haven’t studied dynamic instance variables yet, so for now we’ll consider the first three types of loops. For information on for-in and for-each-in, see Chapter 15. The while Statement Structurally, a while statement is constructed much like an if statement: a main state- ment encloses a code block that is executed only when a given test expression is true: while (testExpression) { codeBlock } If testExpression is true, the code in codeBlock (called the loop body) is executed. But, unlike the if statement, when the codeBlock is finished, execution begins again at the beginning of the while statement (that is, ActionScript “loops” back to the begin- ning of the while statement). The second pass through the while statement works just like the first: the testExpression is evaluated, and if it is still true, codeBlock is exe- cuted again. This process continues until testExpression becomes false, at which point execution continues with any statements that follow the while statement in the program. If testExpression never yields false, the loop executes infinitely, eventu- ally causing the Flash runtime to generate an error, which stops the loop (and all cur- rently executing code). To avoid infinite execution, a while loop’s codeBlock typically includes a statement that modifies the testExpression, causing it to yield false when some condition is met. For example, consider the following loop, which calculates 2 to the power of 3 (i.e., 2 times 2 times 2) by executing the loop body two times: var total = 2; var counter = 0; Loops | 51 while (counter < 2) { total = total * 2; counter = counter + 1; } To execute the preceding while loop, ActionScript first evaluates the test expression: counter < 2 Because counter is 0, and 0 is less than 2, the value of the test expression is true; so, ActionScript executes the loop body: total = total * 2; counter = counter + 1; The loop body sets total to its own value multiplied by two and adds one to counter. Hence, total becomes 4, counter becomes 1. When the loop body com- pletes, it’s time to repeat the loop. The second time the loop executes, ActionScript once again checks the value of the test expression. This time, counter’s value is 1, and 1 is still less than 2, so the value of the test expression is, once again, true. Consequently, ActionScript executes the loop body for a second time. As before, the loop body sets total to its own value multiplied by two and adds one to counter. Hence, total becomes 8, counter becomes 2. When the loop body completes, it’s again time to repeat the loop. The third time the loop executes, ActionScript once again checks the value of the test expression. This time, counter’s value is 2, which is not less than 2, so the value of the test expression is false, and the loop ends. When the entire process is complete, total, which started with the value 2, has been multiplied by itself two times, so it ends up with the value 8. In real code, you should use Math.pow( )—not a loop statement—to perform exponential calculations. For example, to calculate 2 to the power of 3, use Math.pow(2, 3). While not particularly thrilling, the preceding loop provides great flexibility. For example, if we wanted to calculate, say, 2 to the power 16, we would simply update the number in the test expression to make the loop body run 15 times, as follows: var total = 2; var counter = 0; while (counter < 15) { total = total * 2; counter = counter + 1; } // Here, total has the value 65536 One execution of a loop body is known as an iteration. Accordingly, a variable, such as counter, that controls the number of times a given loop iterates is known as the 52 | Chapter 2: Conditionals and Loops loop iterator, or, sometimes, the loop index. By convention, loop iterators are typi- cally named i, as shown in the following code: var total = 2; var i = 0; while (i < 15) { total = total * 2; i = i + 1; } The last line of the preceding loop body is known as the loop update because it updates the value of the iterator in a way that will eventually cause the loop to end. In this case, the loop update adds one to the value of the loop iterator. Adding one to the value of the loop iterator is such a common task that it has its own operator: the increment operator, written as ++. The increment operator adds one to the value of its operand. For example, the following code adds one to the variable n: var n = 0; n++; // n's value is now 1 The following code revises our loop to use the increment operator: var total = 2; var i = 0; while (i < 15) { total = total * 2; i++; } The opposite of the increment operator is the decrement operator, written as --. The decrement operator subtracts one from the value of its operand. For example, the following code subtracts one from the variable n: var n = 4; n--; // n's value is now 3 The decrement operator is often used with loops that count down from a given value, rather than counting up (as our preceding examples did). We’ll see both the increment and decrement operators used throughout this book. However, in general, the increment operator is used much more frequently than the decrement operator. Processing Lists with Loops Loops are typically used to process lists of things. For example, suppose we’re creating a registration form that requires the user to sub- mit an email address. Before the form is submitted to the server, we want to check whether the supplied email address contains an @ sign. If it doesn’t, we’ll warn the user that the email address is invalid. Loops | 53 Note that in this example, our concept of a “valid” address is extremely rudimentary. For example, in our code, addresses that start or end with an @ character, or that contain multiple @ characters, are considered valid. Nevertheless, our example shows a decent first step towards creating an email validation algorithm. To check for the @ sign in the email address, we’ll use a loop that treats the email address as a list of individual characters. Before we start the loop, we’ll create a vari- able, isValidAddress, and set it to false. The loop body will execute once for each character in the email address. The first time the loop body executes, it checks whether the first character in the email address is an @ sign. If it is, the loop body sets isValidAddress to true, indicating that the email address is valid. The second time the loop body executes, it checks whether the second character in the email address is an @ sign. Once again, if the @ sign is found, the loop body sets isValidAddress to true, indicating that the email address is valid. The loop body continues checking each character in the email address until there are no more characters to check. At the end of the loop, if isValidAddress is still false, then the @ sign was never found, so the email address is invalid. If, on the other hand, isValidAddress is true, then the @ sign was found, so the email address is valid. Now let’s take a look at the actual validation code. In a real application, we’d start by retrieving the user’s supplied email address, However, for the sake of simplicity in this example will supply the address manually, as follows: var address = "me@moock.org"; Next, we create the isValidAddress variable and set it to false: var isValidAddress = false; Then, we create our loop iterator: var i = 0; Next comes the while statement for our loop. We want it to run once for every char- acter in address. To retrieve the number of letters in a string, we use the String class’s instance variable length. For example, the value of the expression "abc".length is 3, indicating that there are three letters in the string “abc”. Accordingly, the basic struc- ture of our loop is as follows: while (i < address.length) { i++; } Each time the loop body runs, we must retrieve one of the characters in address and compare it to the string “@”. If the retrieved character is equal to “@”, then we’ll set isValidAddress to true. To retrieve a specific character from a string, we use the built-in String class’s instance method charAt( ). The name “charAt” is short for 54 | Chapter 2: Conditionals and Loops “character at”. The charAt( ) method expects one argument—a number specifying the position, or index, of the character to retrieve. Character indices start at zero. For example, the following call expression has the value “m” because the character at index 0 is “m”: address.charAt(0); Likewise, the following call expression has the value “@” because the character at index 2 is “@”: address.charAt(2); In our loop body, the indexof the character to retrieve is specified dynamically by the loop iterator, i, as shown in the following code: while (i < address.length) { if (address.charAt(i) == "@") { isValidAddress = true; } i++; } Here’s the validation code in its entirety: var address = "me@moock.org"; var isValidAddress = false; var i = 0; while (i < address.length) { if (address.charAt(i) == "@") { isValidAddress = true; } i++; } For practice, let’s examine how ActionScript would execute the preceding while statement. First, ActionScript evaluates the test expression: i < address.length In this case, i is 0, and address.length is 12. The number 0 is less than 12, so the value of the test expression is true, and ActionScript executes the loop body: if (address.charAt(i) == "@") { isValidAddress = true; } i++; In the loop body, ActionScript must first determine whether to execute the code in the conditional: if (address.charAt(i) == "@") { isValidAddress = true; } Loops | 55 To decide whether to execute the code in the preceding conditional, ActionScript checks whether address.charAt(i) is equal to “@”. The first time the loop body exe- cutes, i is 0, so address.charAt(i) evaluates to address.charAt(0), which, as we saw earlier, yields the character “m” (the first character in the email address). The charac- ter “m” is not equal to the character “@”, so ActionScript does not execute the code in the conditional. Next, ActionScript executes the loop update, incrementing i’s value to 1: i++; With the loop body complete, it’s time to repeat the loop. The second time the loop executes, ActionScript once again checks the value of the test expression. This time, i is 1, and address.length is still 12. The number 1 is less than 12, so the value of the test expression is true, and ActionScript executes the loop body for the second time. As before, in the loop body, ActionScript must deter- mine whether to execute the code in the conditional: if (address.charAt(i) == "@") { isValidAddress = true; } This time, i is 1, so address.charAt(i) evaluates to address.charAt(1), which yields the character “e” (the second character in the email address). The character “e” is again not equal to the character “@”, so ActionScript does not execute the code in the conditional. Next, ActionScript executes the loop update, incrementing i’s value to 2. Again, it’s time to repeat the loop. The third time the loop executes, ActionScript checks the value of the test expres- sion. This time, i is 2, and address.length is still 12. The number 2 is less than 12, so the value of the test expression is true, and ActionScript executes the loop body for the third time. As before, in the loop body, ActionScript must determine whether to execute the code in the conditional: if (address.charAt(i) == "@") { isValidAddress = true; } This time, i is 2, so address.charAt(i) evaluates to address.charAt(2), which yields the character “@”. The character “@” is equal to the character “@”, so ActionScript executes the code in the conditional, setting isValidAddress to true. Then, Action- Script executes the loop update, incrementing i’s value to 3. The loop repeats in the same way nine more times. When the entire process is com- plete, isValidAddress has been set to true, so the program knows that the email address can safely be submitted to the server for processing. 56 | Chapter 2: Conditionals and Loops Ending a Loop with the break Statement The loop presented in the preceding section was effective but inefficient. According to the hypothetical address-checker’s simple logic, an email address is considered valid if it contains the @ character. To check for the @ character, the loop in the pre- ceding section examined every single character in the supplied email address. In the case of the example email address "me@moock.org", the loop body executed a full 12 times, even though the address was known to be valid after the third character was examined. Hence, the loop body executed needlessly nine times. To make the loop from the preceding section more efficient, we can use the break statement, which immediately terminates a loop. Here’s the updated code: var address = "me@moock.org"; var isValidAddress = false; var i = 0; while (i < address.length) { if (address.charAt(i) == "@") { isValidAddress = true; break; } i++; } In the preceding loop, as soon as an @ character is found in address, isValidAddress is set to true, and then the break statement causes the loop to terminate. If you create a loop whose job is to find something in a list, always use break to terminate that loop when it finds what it’s looking for. Reader exercise: See if you can update the preceding loop to reject addresses that start or end with an @ character, or contain multiple @ characters. You might also try to update the loop to reject addresses that contain no . character. The do-while Statement As we saw earlier, a while statement tells ActionScript to execute a block of code repeatedly while a specified condition remains true. Due to a while loop’s structure, its body will be skipped entirely if the loop’s test expression is not true the first time it is tested. A do-while statement lets us guarantee that a loop body will be executed at least once with minimal fuss. The body of a do-while loop always executes the first time through the loop. The do-while statement’s syntaxis somewhat like an inverted while statement: do { codeBlock } while (testExpression); Loops | 57 The keyword do begins the loop, followed by the codeBlock of the body. On the first pass through the do-while loop, the codeBlock is executed before the testExpression is ever checked. At the end of the codeBlock block, if testExpression is true, the loop is begun anew, and the codeBlock is executed again. The loop executes repeatedly until testExpression is false, at which point the do-while statement ends. The for Statement A for loop is essentially synonymous with a while loop, but it is written with more compact syntax. The for loop places the loop initialization and update statements together with test expression, at the top of the loop. Here’s the syntax of the for loop: for (initialization; testExpression; update) { codeBlock } Before the first iteration of a for loop, the initialization statement is performed (once and only once). It is typically used to set the initial value of one or more itera- tor variables. As with other loops, if testExpression is true, then the codeBlock is executed. Otherwise, the loop ends. Even though it appears in the loop header, the update statement is executed at the end of each loop iteration, before testExpression is tested again to see if the loop should continue. Here’s a for loop that calculates 2 to the power 3: var total = 2; for (var i = 0; i < 2; i++) { total = total * 2; } For comparison, here’s the equivalent while loop: var total = 2; var i = 0; while (i < 2) { total = total * 2; i++; } Here’s a for loop that checks to see whether a string contains the @ character. It is functionally identical to our earlier while loop that performs the same task: var address = "me@moock.org"; var isValidAddress = false; for (var i = 0; i < address.length; i++) { if (address.charAt(i) == "@") { isValidAddress = true; break; 58 | Chapter 2: Conditionals and Loops } } Once you’re used to the for syntax, you’ll find it saves space and allows for easy interpretation of the loop’s body and controls. Boolean Logic Early in this chapter, we saw how to make logical decisions using test expressions that yield Boolean values. The decisions were based on a single factor, such as “if language is "english", then display "Hello"”. But not all programming logic is so sim- ple. Programs often need to consider multiple factors in branching logic (i.e., deci- sion making). To manage multiple factors in a test expression, we use the Boolean operators: || (logical OR) and && (logical AND). Logical OR The logical OR operator is most commonly used to initiate some action when at least one of two conditions is met. For example, “If I am hungry or I am thirsty, I’ll go to the kitchen.” The symbol for logical OR is made using two “pipe” characters: ||. Typically, the pipe character (|) is accessible using the Shift key and the Backslash (\) key in the upper right of most Western keyboards, where it may be depicted as a dashed vertical line. Logical OR has the following general form: expression1 || expression2 When both expression1 and expression2 are Boolean values or evaluate to Boolean values, logical OR returns true if either expression is true and returns false only if both expression are false. In summary: true || false // true because first operand is true false || true // true because second operand is true true || true // true (however, either operand being true is sufficient) false || false // false because both operands are false When expression1 is not a Boolean value, ActionScript first converts it to a Boolean; if the result of such a conversion is true, logical OR returns expression1’s resolved value. Otherwise, logical OR returns expression2’s resolved value. Here’s some code to demonstrate: 0 || "hi there!" // expression1 does not convert to true, so the // operation returns expression2's value: "hi there!" "hey" || "dude" // expression1 is a nonempty string, so it converts to // true and the operation returns // expression1's value: "hey" false || 5 + 5 // expression1 does not convert to true, so the // value of expression2 (namely 10) is returned. Boolean Logic | 59 The results of converting various kinds of data to a Boolean value are listed in the section “Conversion to Primitive Types” in Chapter 8. In practice, we rarely use non-Boolean values returned by a logical OR expression. Instead, we normally use the result in a conditional statement where it is used to make a Boolean decision. Consider the following code: var x = 10; var y = 15; if (x || y) { // This code executes if one of either x or y is not zero } On line 3, we see a logical OR operation (x||y) being used where a Boolean is expected as the test expression of an if statement. The first step in determining the value of x||yis to convert 10 (the value of the first operand, x) to a Boolean. Any nonzero finite number converts to the Boolean true. Hence, the logical OR returns the value of x, which is 10. So, to ActionScript, the if statement looks like this: if (10) { // This code executes if one of either x or y is not zero } But 10 is a number, not a Boolean. So what happens next? The if statement converts the return value of the logical OR operation to a Boolean. In this case, 10 is con- verted to the Boolean value true, and ActionScript sees our code as: if (true) { // This code executes if one of either x or y is not zero } And there you have it. The test expression is true, so the code between the curly braces is executed. Note that if the first expression in a logical OR operation resolves to true, it is unnec- essary, and therefore inefficient, to evaluate the second expression. Hence, Action- Script evaluates the second expression only if the first expression resolves to false. This fact is useful in cases in which you don’t want to resolve the second expression unless the first expression resolves to false. In the following example, we check if a number is out of range. If the number is too small, there is no need to perform the second test, in which we check whether it is too large. if (xPosition < 0 || xPosition > 100) { // This code executes if one of either xPosition is between // 0 and 100, inclusive } Note that the variable xPosition must be included in each comparison. The follow- ing code shows a common mistaken attempt to check xPosition’s value twice: // Oops! Forgot xPosition in the comparison with 100 if (xPosition < 0 || > 100) { // This code executes if one of either xPosition is between 60 | Chapter 2: Conditionals and Loops // 0 and 100, inclusive } Logical AND Like the logical OR operator, logical AND is used primarily to execute a block of code conditionally—in this case, only when both of two conditions are met. The log- ical AND operator has the following general form: expression1 && expression2 Both expression1 and expression2 can be any valid expression. In the simplest case, in which both expressions are Boolean values, logical AND returns false if either operand is false and returns true only if both operands are true. In summary: true && false // false because second expression is false false && true // false because first expression is false true && true // true because both expressions are true false && false // false because both expressions are false // (either is sufficient) Let’s see how the logical AND operator is used in two examples. First, we execute some code only when two variables are both greater than 50: x = 100; y = 51; if (x>50 && y>50) { // Code here executes only if x and y are greater than 50 } Next, imagine a New Year’s Day-contest web site in which users are granted access only when they provide the correct password, and the current date is January 1. The following code shows how to use the AND operator to determine whether both con- ditions have been met (the correct password is “fun”): var now = new Date( ); // Create a new Date object var day = now.getDate( ); // Returns an integer between 1 and 31 var month = now.getMonth( ); // Returns an integer between 0 and 11 if ( password=="fun" && (month + day)>1 ) { // Let the user in... } The technical behavior of the logical AND operator is quite similar to that of the log- ical OR operator. First, expression1 is converted to a Boolean. If the result of that conversion is false, the value of expression1 is returned. If the result of that conver- sion is true, the value of expression2 is returned. As with OR, if the first expression in a logical AND operation resolves to false,itis unnecessary, and therefore inefficient, to evaluate the second expression. Therefore, ActionScript evaluates the second expression only if the first expression resolves to Boolean Logic | 61 true. This fact is useful in cases in which you don’t want to resolve the second expression unless the first operand resolves to true. In this example, we perform a division operation only if the divisor is nonzero: if ( numItems!=0 && totalCost/numItems>3 ) { // Execute this code only when the number of items is not equal // to 0, and the total cost of each item is greater than 3 } Logical NOT The logical NOT operator (!) returns the Boolean opposite of its single operand. It takes the general form: !expression If expression is true, logical NOT returns false.Ifexpression is false, logical NOT returns true.Ifexpression is not a Boolean, its value is converted to a Boolean for the sake of the operation, and its opposite is returned. Like the does-not-equal operator (!=), the logical NOT operator is convenient for testing what something isn’t rather than what it is. For example, the body of the fol- lowing conditional statement executes only when the current date is not January 1. Notice the extra parentheses, which force a custom order of operations (prece- dence), as discussed in Chapter 10. var now = new Date( ); // Create a new Date object var day = now.getDate( ); // Returns an integer between 1 and 31 var month = now.getMonth( ); // Returns an integer between 0 and 11 if ( !( (month + day)==1) ) { // Execute "not-January 1st" code } The NOT operator is also sometimes used to toggle a variable from true to false and vice versa. For example, suppose we have a single button that is used to turn an application’s sound on and off. When the button is pressed, the program might use the following code to enable or disable audio playback: soundEnabled = !soundEnabled // Toggle the current sound state if (soundEnabled) { // Make sure sounds are audible } else { // Mute all sounds } Notice that ! is also used in the inequality operator (!=). As a programming symbol, the ! character usually means not,oropposite. It is unrelated to the ! symbol used to indicate “factorial” in common mathematical notation. 62 | Chapter 2: Conditionals and Loops Back to Classes and Objects We’re now done with our introduction to conditionals and loops, but we definitely haven’t seen the last of them. Over the course of this book, we’ll encounter plenty of examples of conditionals and loops used in real-world situations. In the next chapter, we’ll return to the general topics of classes and objects. If you’ve been yearning for our virtual pets, read on. 63 Chapter 3 CHAPTER 3 Instance Methods Revisited4 In Chapter 1, we learned how to create instance methods. In this chapter, we’ll expand that basic knowledge by studying the following additional instance-method topics: • Omitting the this keyword • Bound methods • State-retrieval and state-modification methods • Get and set methods • Extra arguments Along the way, we’ll continue developing the virtual zoo program that we started in Chapter 1. But before we begin, take a minute to reacquaint yourself with the virtual zoo program. Example 3-1 shows the code as we last saw it. Example 3-1. Zoo program // VirtualPet class package zoo { internal class VirtualPet { internal var petName; private var currentCalories = 1000; private var creationTime; public function VirtualPet (name) { this.creationTime = new Date( ); this.petName = name; } public function eat (numberOfCalories) { this.currentCalories += numberOfCalories; } public function getAge ( ) { var currentTime = new Date( ); var age = currentTime.time - this.creationTime.time; 64 | Chapter 3: Instance Methods Revisited Omitting the this Keyword In Chapter 1, we learned that the this keyword is used to refer to the current object within constructor methods and instance methods. For example, in the following code, the expression this.petName = name tells ActionScript to set the value of the instance variable petName on the object currently being created: public function VirtualPet (name) { this.petName = name; } In the following code, the expression this.currentCalories += numberOfCalories tells ActionScript to set the value of the instance variable currentCalories on the object through which the eat( ) method was invoked: public function eat (numberOfCalories) { this.currentCalories += numberOfCalories; } In code that frequently accesses the variables and methods of the current object, including this can be laborious and can lead to clutter. To reduce labor and improve readability, ActionScript generally allows the current object’s instance vari- ables and instance methods to be accessed without this. Here’s how it works: within a constructor method or an instance method, when ActionScript encounters an identifier in an expression, it searches for a local vari- able, parameter, or nested function whose name matches that identifier. (Nested functions are discussed in Chapter 5.) If no local variable, parameter, or nested func- tion’s name matches the identifier, then ActionScript automatically searches for an instance variable or instance method whose name matches the identifier. If a match is found, then the matching instance variable or instance method is used in the expression. return age; } } } // VirtualZoo class package zoo { public class VirtualZoo { private var pet; public function VirtualZoo ( ) { this.pet = new VirtualPet("Stan"); } } } Example 3-1. Zoo program (continued) Omitting the this Keyword | 65 For example, consider what happens if we remove the keyword this from the eat( ) method, as follows: public function eat (numberOfCalories) { currentCalories += numberOfCalories; } When the preceding method runs, ActionScript encounters numberOfCalories, and tries to find a local variable, parameter, or nested function by that name. There is a parameter by that name, so its value is used in the expression (in place of numberOfCalories). Next, ActionScript encounters currentCalories, and tries to find a local variable, parameter, or nested function by that name. No variable, parameter, or nested func- tion named currentCalories is found, so ActionScript then tries to find an instance variable or instance method by that name. This time, ActionScript’s search is suc- cessful: the VirtualPet class does have an instance variable named currentCalories, so ActionScript uses it in the expression. As a result, the value of numberOfCalories is added to the instance variable currentCalories. Therefore, within the eat( ) method, the expression this.currentCalories and currentCalories are identical. For the sake of easier reading, many developers (and this book) avoid redundant uses of this. From now on, we’ll omit this when referring to instance variables and instance methods. However, some programmers prefer to always use this, simply to distinguish instance variables and instance methods from local variables. Note that use of the this keyword is legal within instance methods, constructor methods, functions, and code in the global scope only. (Global scope is discussed in Chapter 16.) Elsewhere, using this generates a compile-time error. The process ActionScript follows to look up identifiers is known as identifier resolution. As discussed in Chapter 16, identifiers are resolved based on the region (or scope) of the program in which they occur. Managing Parameter/Variable Name Conflicts When an instance variable and a method parameter have the same name, we can access the variable by including the this keyword (known as disambiguating the vari- able from the parameter). For example, the following revised version of VirtualPet shows the eat( ) method with a parameter, calories, whose name is identical to (i.e., conflicts with) an instance variable named calories: package zoo { internal class VirtualPet { // Instance variable 'calories' private var calories = 1000; 66 | Chapter 3: Instance Methods Revisited // Method with parameter 'calories' public function eat (calories) { this.calories += calories; } } } Within the body of eat( ), the expression calories (with no this) refers to the method parameter and the expression this.calories (with this) refers to the instance variable. The calories parameter is said to shadow the calories instance variable because on its own, the identifier calories refers to the parameter, not the instance variable. The instance variable can be accessed only with the help of the keyword this. Note that like parameters, local variables can also shadow instance variables and instance methods of the same name. A local variable also shadows a method parameter of the same name, effectively redefining the parameter and leav- ing the program with no way to refer to the parameter. Many programmers purposely use the same name for a parameter and an instance variable, and rely on this to disambiguate the two. To keep things more clearly sepa- rated in your own code, however, you can simply avoid using parameter names that have the same name as instance variables, instance methods, or local variables. Now let’s move on to our next instance-method topic, bound methods. Bound Methods In ActionScript, a method can, itself, be treated as a value. That is, a method can be assigned to a variable, passed to function or another method, or returned from a function or another method. For example, the following code creates a new VirtualPet object, and then assigns that object’s eat( ) method to the local variable consume. Notice that in the assign- ment statement, the method-call parentheses, (), are not included after the method name. As a result, the method itself—not the method’s return value—is assigned to the variable consume. package zoo { public class VirtualZoo { private var pet; public function VirtualZoo ( ) { pet = new VirtualPet("Stan"); // Assign the method eat( ) to a variable var consume = pet.eat; } } } Bound Methods | 67 A method assigned to a variable can be invoked via that variable using the standard parentheses operator, (). For example, in the following code, we invoke the method referenced by the variable consume: package zoo { public class VirtualZoo { private var pet; public function VirtualZoo ( ) { pet = new VirtualPet("Stan"); // Assign a bound method to consume var consume = pet.eat; // Invoke the method referenced by consume consume(300); } } } When the preceding bolded code runs, the eat( ) method is invoked and passed the argument 300. The question is, which pet eats the food? Or, put more technically, on which object does the method execute? When a method is assigned to a variable and then invoked through that variable, it executes on the object through which it was originally referenced. For example, in the preceding code, when the eat( ) method is assigned to the variable consume,itis referenced through the VirtualPet object with the name “Stan”. Hence, when eat( ) is invoked via consume, it executes on the VirtualPet object with the name “Stan”. A method that is assigned to a variable, passed to a function or method, or returned from a function or method is known as a bound method. Bound methods are so named because each bound method is permanently linked to the object through which it was originally referenced. Bound methods are considered instances of the built-in Function class. When invoking a bound method, we need not specify the object on which the method should execute. Instead, the bound method will automatically execute on the object through which it was originally referenced. Within the body of a bound method, the keyword this refers to the object to which the method is bound. For example, within the body of the bound method assigned to consume, this refers to the VirtualPet object named “Stan”. Bound methods are typically used when one section of a program wishes to instruct another section of the program to invoke a particular method on a particular object. For examples of such a scenario, see the discussion of event handling in Chapter 12. ActionScript’s event-handling system makes extensive use of bound methods. Continuing with this chapter’s instance-method theme, the next section describes how instance methods can modify an object’s state. 68 | Chapter 3: Instance Methods Revisited Using Methods to Examine and Modify an Object’s State Earlier we learned that it’s good object-oriented practice to declare instance vari- ables private, meaning that they cannot be read or modified by code outside of the class in which they are defined. Good object-oriented practice dictates that, rather than allow external code to modify instance variables directly, we should instead define instance methods for examining or changing an object’s state. For example, earlier, we gave our VirtualPet class an instance variable named currentCalories. The currentCalories variable conceptually describes the state of each pet’s hunger. To allow external code to reduce the pet’s hunger level, we could make currentCalories publicly accessible. External code could then set the pet’s hunger state to any arbitrary value, as shown in the following code: somePet.currentCalories = 5000; The preceding approach, however, is flawed. If external code can modify currentCalories directly, then the VirtualPet class has no way to ensure that the value assigned to that variable is legal, or sensible. For example, external code might assign currentCalories 1000000, causing the pet to live for hundreds of years with- out getting hungry. Or external code might assign currentCalories a negative value, which might cause the program to malfunction. To prevent these problems, we should declare currentCalories as private (as we did earlier in our VirtualPet class). Rather than allowing external code to modify currentCalories directly, we instead provide one or more public instance methods that can be used to change each pet’s state of hunger in a legitimate way. Our exist- ing VirtualPet class already provides a method, eat( ), for reducing a pet’s hunger. However, the eat( ) method allows any number of calories to be added to currentCalories. Let’s now update the VirtualPet class’s eat( ) method so that it pre- vents the value of currentCalories from exceeding 2,000. Here’s the original code for the eat( ) method: public function eat (numberOfCalories) { currentCalories += numberOfCalories; } To restrict currentCalories’s value to a maximum of 2,000, we simply add an if statement to the eat( ) method, as follows: public function eat (numberOfCalories) { // Calculate the proposed new total calories for this pet var newCurrentCalories = currentCalories + numberOfCalories; // If the proposed new total calories for this pet is greater // than the maximum allowed (which is 2000)... if (newCurrentCalories > 2000) { // ...set currentCalories to its maximum allowed value (2000) currentCalories = 2000; } else { // ...otherwise, increase currentCalories by the specified amount Using Methods to Examine and Modify an Object’s State | 69 currentCalories = newCurrentCalories; } } The VirtualPet class’s eat( ) method provides a safe means for external code to mod- ify a given VirtualPet object’s hunger. However, thus far, the VirtualPet class does not provide a means for external code to determine how hungry a given VirtualPet object is. To give external code access to that information, let’s define a method, getHunger( ), which returns the number of calories a VirtualPet object has left, as a percentage. Here’s the new method: public function getHunger ( ) { return currentCalories / 2000; } We now have methods for retrieving and modifying a VirtualPet object’s current state of hunger (getHunger( ) and eat( )). In traditional object-oriented terminology, a method that retrieves the state of an object is known as an accessor method, or more casually, a getter method. By contrast, a method that modifies the state of an object is known as a mutator method, or more casually, a setter method. However, in Action- Script 3.0, the term “accessor method” refers to a special variety of method that is invoked using variable read- and write-syntax, as described later in the section “Get and Set Methods.” As noted earlier, to avoid confusion in this book, we’ll avoid using the traditional terms accessor, mutator, getter, and setter. Instead, we’ll use the unofficial terms retriever method and modifier method when discussing accessor methods and mutator methods. Furthermore, we’ll use the terms “get method” and “set method” only when referring to ActionScript’s special automatic methods. For a little more practice with retriever and modifier methods, let’s update the VirtualPet class again. Previously, to retrieve and assign a VirtualPet object’s name, we accessed the petName variable directly, as shown in the following code: somePet.petName = "Erik"; The preceding approach, however, could prove problematic later in our program. It allows petName to be assigned a very long value that might not fit on screen when we display the pet’s name. It also allows petName to be assigned an empty string (""), which would not appear on screen at all. To prevent these problems, let’s make petName private, and define a modifier method for setting a pet’s name. Our modifier method, setName( ), imposes a maximum name length of 20 characters and rejects attempts to set petName to an empty string (""). Here’s the code: public function setName (newName) { // If the proposed new name has more than 20 characters... if (newName.length > 20) { // ...truncate it using the built-in String.substr( ) method, // which returns the specified portion of the string on which // it is invoked newName = newName.substr(0, 20); } else if (newName == "") { // ...otherwise, if the proposed new name is an empty string, 70 | Chapter 3: Instance Methods Revisited // then terminate this method without changing petName return; } // Assign the new, validated name to petName petName = newName; } Now that we’ve made petName private, we need to provide a retriever method through which external code can access a VirtualPet object’s name. We’ll name our retriever method, getName( ). For now, getName( ) will simply return the value of petName. (Returning an instance variable’s value is often all a retriever method does.) Here’s the code: public function getName ( ) { return petName; } The getName( ) method is currently very simple, but it gives our program flexibility. For example, in the future, we may decide we want to make pet names gender-spe- cific. To do so, we simply update getName( ), as follows (the following hypothetical version of getName( ) assumes that VirtualPet defines an instance variable, gender, indicating the gender of each pet): public function getName ( ) { if (gender == "male") { return "Mr. " + petName; } else { return "Mrs. " + petName; } } Example 3-2 shows the new code for the VirtualPet class, complete with the getName( ) and setName( ) methods. For the sake of simplicity, the instance method getAge( ) and the instance variable creationTime have been removed from the VirtualPet class. Example 3-2. The VirtualPet class package zoo { internal class VirtualPet { private var petName; private var currentCalories = 1000; public function VirtualPet (name) { petName = name; } public function eat (numberOfCalories) { var newCurrentCalories = currentCalories + numberOfCalories; if (newCurrentCalories > 2000) { currentCalories = 2000; } else { currentCalories = newCurrentCalories; Using Methods to Examine and Modify an Object’s State | 71 Here’s a sample use of our new getName( ) and setName( ) methods: package zoo { public class VirtualZoo { private var pet; public function VirtualZoo ( ) { pet = new VirtualPet("Stan"); // Assign the pet's old name to the local variable oldName var oldName = pet.getName( ); // Give the pet a new name pet.setName("Marcos"); } } } By using a modifier method to mediate variable-value assignments, we can develop applications that respond gracefully to runtime problems by anticipating and han- dling illegal or inappropriate values. But does that mean each and every instance variable access in a program should happen through a method? For example, con- sider our VirtualPet constructor method: public function VirtualPet (name) { petName = name; } } } public function getHunger ( ) { return currentCalories / 2000; } public function setName (newName) { // If the proposed new name has more than 20 characters... if (newName.length > 20) { // ...truncate it newName = newName.substr(0, 20); } else if (newName == "") { // ...otherwise, if the proposed new name is an empty string, // then terminate this method without changing petName return; } // Assign the new, validated name to petName petName = newName; } public function getName ( ) { return petName; } } } Example 3-2. The VirtualPet class (continued) 72 | Chapter 3: Instance Methods Revisited Now that we have a method for setting petName, should we update the VirtualPet constructor method as follows? public function VirtualPet (name) { setName(name); } The answer depends on the circumstances at hand. Generally speaking, it’s quite rea- sonable to access private variables directly within the class that defines them. How- ever, when a variable’s name or role is likely to change in the future, or when a modifier or retriever method provides special services during variable access (such as error checking), it pays to use the method everywhere, even within the class that defines the variables. For example, in the preceding updated VirtualPet constructor method, it’s wise to set petName through setName( ) because setName( ) guarantees that the supplied name is neither too long nor too short. That said, in cases where speed is a factor, direct variable access may be prudent (accessing a variable directly is always faster than accessing it through a method). Programmers who prefer the style of direct variable access but still want the benefits of retriever and modifier methods, typically use ActionScript’s automatic get and set methods, discussed next. Get and Set Methods In the previous section we learned about retriever and modifier methods, which are public methods that retrieve and modify an object’s state. Some developers consider such methods cumbersome. They argue that: pet.setName("Jeff"); is more awkward than: pet.name = "Jeff"; In our earlier study, we saw that direct variable assignments such as pet.name = "Jeff" aren’t ideal object-oriented practice and can lead to invalid variable assignments. To bridge the gap between the convenience of variable assignment and the safety of retriever and modifier methods, ActionScript supports get and set methods. These methods are invoked using variable retrieval- and assignment-syntax. To define a get method, we use the following general syntax: function get methodName ( ) { statements } where the keyword get identifies the method as a get method, methodName is the method’s name, and statements is zero or more statements executed when the method is invoked (one of which is expected to return the value associated with methodName). Get and Set Methods | 73 To define a set method, we use the following general syntax: function set methodName (newValue) { statements } where the set keyword identifies the method as a set method, methodName is the method’s name, newValue receives the value assigned to an internal instance variable, and statements is zero or more statements executed when the method is invoked. The statements are expected to determine and internally store the value associated with methodName. Note that in a set method body, the return statement must not be used to return a value (but can be used on its own to terminate the method). Set methods have an automatic return value, discussed later. Get and set methods have a unique style of being invoked that does not require use of the function call operator, (). A get method, x( ), on an object, obj, is invoked as follows: obj.x; rather than: obj.x( ); A set method, y( ), on an object, obj, is invoked as follows: obj.y = value; rather than: obj.y(value); where value is the first (and only) argument passed to y( ). Get and set methods, therefore, appear to magically translate variable access into method calls. As an example, let’s (temporarily) add a get method named name( ) to our VirtualPet class. Here’s the code for the method: public function get name ( ) { return petName; } With the get method name( ) in place, all attempts to retrieve the value of the instance variable name actually invoke the get method. The get method’s return value appears as though it were the value of the name variable. For example, the following code invokes the get method name( ) and assigns its return value to the variable oldName: var oldName = pet.name; Now let’s (temporarily) add a set method named name( ) to our VirtualPet class. Here’s the code for the method: public function set name (newName) { // If the proposed new name has more than 20 characters... if (newName.length > 20) { 74 | Chapter 3: Instance Methods Revisited // ...truncate it newName = newName.substr(0, 20); } else if (newName == "") { // ...otherwise, if the proposed new name is an empty string, // then terminate this method without changing petName return; } // Assign the new, validated name to petName petName = newName; } With the set method name( ) in place, attempts to assign the value of the instance variable name invoke the set method. The value used in the name assignment state- ment is passed to the set method, which stores it internally in the private variable petName. For example, the following code invokes the set method name( ), which stores “Andreas” internally in petName: pet.name = "Andreas"; With a get and a set method named name( ) defined, the name variable becomes an external façade only; it does not exist as a variable in the class but can be used as though it did. You can, therefore, think of instance variables that are backed by get and set methods (such as name) as pseudo-variables. It is illegal to create an actual variable with the same name as a get or set method. Attempts to do so result in a compile-time error. When a set method is called, it always invokes its corresponding get method and returns the get method’s return value. This allows a program to use the new value immediately after setting it. For example, the following code shows a fragment of a fictitious music player application. It uses a set method call to tell the music player which song to play first. It then immediately plays that song by calling start( ) on the return value of the firstSong assignment. // Invoke start( ) on new Song("dancehit.mp3")--the return value // of the set method firstSong( ) (musicPlayer.firstSong = new Song("dancehit.mp3")).start( ); While convenient in some cases, the return-value feature of set methods imposes lim- its on get methods: specifically, get methods should never perform tasks beyond those required to retrieve their internal variable value. For example, a get method should not implement a global counter that tracks how many times a variable has been accessed. The automatic invocation of the get method by the set method would tarnish the counter’s record keeping. A get/set pseudo-variable can be made read-only by declaring a get method without declaring a set method. Handling an Unknown Number of Parameters | 75 Choosing between retriever/modifier methods and get/set methods is a matter of personal taste. This book, for example, does not use get/set methods, but you should expect to see them used by other programmers and in some documentation. Moving on, to complete our study of instance methods, we’ll learn how to handle an unknown number of parameters. The following discussion requires a prior knowl- edge of arrays (ordered lists of values), which we haven’t covered yet. If you are new to arrays, you should skip this section for now and return to it after you have read Chapter 11. The techniques described in the next section apply not only to instance methods, but also static methods and functions, which we’ll study in the coming chapters. Handling an Unknown Number of Parameters In Chapter 1, we learned that it is illegal to call a method without supplying argu- ments for all required parameters. It is also illegal to call a method with more than the required number of arguments. To define a method that accepts an arbitrary number of arguments, we use the ...(rest) parameter. The ...(rest) parameter defines an array to hold any arguments passed to a given method. It can be used on its own or in combination with named parameters. When used on its own, the ...(rest) parameter has the following general form: function methodName (...argumentsArray) { } In the preceding code, methodName is the name of a method (or function), and argumentsArray is the name of a parameter that will be assigned an automatically created array of all arguments received by the method. The first argument (the left- most argument in the call expression) is stored at index 0 and is referred to as argumentsArray[0]. Subsequent arguments are stored in order, proceeding to the right—so, the second argument is argumentsArray[1], the third is argumentsArray[2], and so on. The ...(rest) parameter allows us to create very flexible functions that operate on an arbitrary number of values. For example, the following code shows a method that finds the average value of any numbers it received as arguments: public function getAverage (...numbers) { var total = 0; for (var i = 0; i < numbers.length; i++) { total += numbers [i]; } return total / numbers.length; } 76 | Chapter 3: Instance Methods Revisited Note that the preceding getAverage( ) method works with numeric arguments only. To protect getAverage( ) from being called with nonnumeric arguments, we could use the is operator, discussed in the section “Upcasting and Downcasting” in Chapter 8. The ...(rest) parameter can also be used in combination with named parameters. When used with other parameters, the ...(rest) parameter must be the last parame- ter in the parameter list. For example, consider the following method, initializeUser( ), used to initialize a user in a hypothetical social-networking application. The method defines a single required parameter, name, followed by a ...(rest) parameter named hobbies: public function initializeUser (name, ...hobbies) { } When invoking initializeUser( ), we must supply an argument for the name parame- ter, and we can optionally also supply an additional comma-separated list of hob- bies. Within the method body, name is assigned the value of the first argument passed to the method, and hobbies is assigned an array of all remaining arguments passed to initializeUser( ). For example, if we issue the following method invocation: initializeUser("Hoss", "video games", "snowboarding"); then name is assigned the value "Hoss", and hobbies is assigned the value ["video games", "snowboarding"]. Up Next: Class-Level Information and Behavior We’re now finished our coverage of instance methods and instance variables. As we learned in Chapter 1, instance methods and instance variables define the behavior and characteristics of the objects of a class. In the next chapter, we’ll learn how to create behavior and manage information that pertains not to individual objects, but to an entire class. 77 Chapter 4 CHAPTER 4 Static Variables and Static Methods5 In Chapter 1, we learned how to define the characteristics and behavior of an object using instance variables and instance methods. In this chapter, we’ll learn how to manage information and create functionality that pertains to a class, itself, rather than its instances. Static Variables Over the past several chapters, we’ve had a fair bit of practice working with instance variables, which are variables associated with a particular instance of a class. Static variables, by contrast, are variables associated with a class itself, rather than a partic- ular instance of that class. Static variables are used to keep track of information that relates logically to an entire class, as opposed to information that varies from instance to instance. For example, a class representing a dialog box might use a static variable to specify the default size for new dialog boxinstances, or a class represent- ing a car in a racing game might use a static variable to specify the maximum speed of all car instances. Like instance variables, static variables are created using variable definitions within class definitions, but static variable definitions must also include the static attribute, as shown in the following generalized code: class SomeClass { static var identifier = value; } As with instance variables, access-control modifiers can be used to control the acces- sibility of static variables in a program. The access-control modifiers available for static-variable definitions are identical to those available for instance-variable defini- tions—public, internal, protected, and private. When no modifier is specified, internal (package-wide access) is used. When a modifier is specified, it is typically placed before the static attribute, as shown in the following code: class SomeClass { private static var identifier = value; } 78 | Chapter 4: Static Variables and Static Methods To access a static variable, we provide the name of the class that defines the variable, followed by a dot (.), followed by the name of the variable, as shown in the follow- ing generalized code: SomeClass.identifier = value; Within the class that defines the variable, identifier can also be used on its own (without the leading class name and dot). For example, in a class, A, that defines a static variable v, the expression A.v is identical to the expression v. Nevertheless, to distinguish static variables from instance variables, many developers (and this book) include the leading class name even when it is not strictly required. Static variables and instance variables of the same name can coexist within a class. If a class, A, defines an instance variable named v, and a static variable, also named v, then the identifier v on its own refers to the instance variable, not the static variable. The static variable can be accessed only by including the leading class name, as in A.v. The instance variable is, therefore, said to shadow the static variable. Now let’s add some static variables to our VirtualPet class. As we just learned, static variables are used to keep track of information that relates logically to an entire class and does not vary from instance to instance. There are already two such pieces of information in our VirtualPet class: the maximum length of a pet’s name and the maximum number of calories a pet can consume. To track that information, we’ll add two new static variables: maxNameLength and maxCalories. Our variables are not required outside the VirtualPet class, so we’ll define them as private. The following code shows the maxNameLength and maxCalories definitions, with the rest of the VirtualPet class code omitted in the interest of brevity: package zoo { internal class VirtualPet { private static var maxNameLength = 20; private static var maxCalories = 2000; // Remainder of class not shown... } } With our maxNameLength and maxCalories variables in place, we can now update the getHunger( ), eat( ), and setName( ) methods to use those variables. Example 4-1 shows the latest version of the VirtualPet class, complete with static variables. Changes since the previous version are shown in bold. Notice that, by convention, the class’s static variables are listed before the class’s instance variables. Example 4-1. The VirtualPet class package zoo { internal class VirtualPet { private static var maxNameLength = 20; private static var maxCalories = 2000; Static Variables | 79 In Example 4-1, notice that the maxNameLength and maxCalories variables help central- ize our code. For example, previously, to update the maximum allowed number of characters in a name, we would have had to change the number 20 in two places within the setName method—a process that is both time-consuming and prone to error. Now, to update the maximum allowed number of characters, we simply change the value of maxNameLength, and the entire class updates automatically. private var petName; // Give each pet 50% of the maximum possible calories to start with. private var currentCalories = VirtualPet.maxCalories/2; public function VirtualPet (name) { setName(name); } public function eat (numberOfCalories) { var newCurrentCalories = currentCalories + numberOfCalories; if (newCurrentCalories > VirtualPet.maxCalories) { currentCalories = VirtualPet.maxCalories; } else { currentCalories = newCurrentCalories; } } public function getHunger ( ) { return currentCalories / VirtualPet.maxCalories; } public function setName (newName) { // If the proposed new name has more than maxNameLength characters... if (newName.length > VirtualPet.maxNameLength) { // ...truncate it newName = newName.substr(0, VirtualPet.maxNameLength); } else if (newName == "") { // ...otherwise, if the proposed new name is an empty string, // then terminate this method without changing petName return; } // Assign the new, validated name to petName petName = newName; } public function getName ( ) { return petName; } } } Example 4-1. The VirtualPet class (continued) 80 | Chapter 4: Static Variables and Static Methods Unexplained literal values such as the number 20 in the previous ver- sion of setName( ) are known as “magic values” because they do some- thing important, but their purpose is not self-evident. Avoid using magic values in your code. In many cases, static variables can be used to keep track of values that would otherwise be “magic.” Static variables are often used to maintain settings whose values should not change once a program has started. To prevent a variable’s value from changing, we define that variable as a constant, as discussed in the next section. Constants A constant is a static variable, instance variable, or local variable with a value that, once initialized, remains fixed for the remainder of the program. To create a con- stant, we use standard variable-definition syntax, but with the keyword const instead of var. By convention, constants are named with all capital letters. To create a con- stant static variable, we use the following generalized code directly within a class body: static const IDENTIFIER = value To create a constant instance variable, we use the following generalized code directly within a class body: const IDENTIFIER = value To create a constant local variable, we use the following generalized code within a method or function: const IDENTIFIER = value In the preceding three code examples, IDENTIFIER is the name of the constant, and value is the variable’s initial value. For constant static variables and constant local variables, once value has been assigned by the variable initializer, it can never be reassigned. For constant instance variables, if the program is compiled in strict mode, once value has been assigned by the variable initializer, it can never be reassigned. If the pro- gram is compiled in standard mode, after value has been assigned by the variable ini- tializer, the variable’s value can also be assigned within the constructor function of the class containing the variable definition, but not thereafter. (We’ll learn the differ- ence between strict mode and standard mode compilation in Chapter 7.) Constants are typically used to create static variables whose fixed values define the options for a particular setting in a program. For example, suppose we’re building an alarm clock program that triggers a daily alarm. The alarm has three modes: visual (a blinking icon), audio (a buzzer), or both audio and visual. The alarm clock is repre- sented by a class named AlarmClock. To represent the three alarm modes, the Constants | 81 AlarmClock class defines three constant static variables: MODE_VISUAL, MODE_AUDIO, and MODE_BOTH. Each constant is assigned a numeric value corresponding to its mode. Mode 1 is considered “visual mode,” mode 2 is considered “audio mode,” and mode 3 is considered “both visual and audio mode.” The following code shows the defini- tions for the mode constants: public class AlarmClock { public static const MODE_VISUAL = 1; public static const MODE_AUDIO = 2; public static const MODE_BOTH = 3; } To keep track of the current mode for each AlarmClock instance, the alarm clock class defines an instance variable, mode. To set the mode of an AlarmClock object, we assign one of the mode constants’ values (1, 2, or 3) to the instance variable mode. The following code sets the default mode for new AlarmClock objects to audio-only (mode 2): public class AlarmClock { public static const MODE_VISUAL = 1; public static const MODE_AUDIO = 2; public static const MODE_BOTH = 3; private var mode = AlarmClock.MODE_AUDIO; } When it comes time to signal an alarm, the AlarmClock object takes the appropriate action based on its current mode. The following code shows how an AlarmClock object would use the mode constants to determine which action to take: public class AlarmClock { public static const MODE_VISUAL = 1; public static const MODE_AUDIO = 2; public static const MODE_BOTH = 3; private var mode = AlarmClock.MODE_AUDIO; private function signalAlarm ( ) { if (mode == MODE_VISUAL) { // Display icon } else if (mode == MODE_AUDIO) { // Play sound } else if (mode == MODE_BOTH) { // Display icon and play sound } } } Note that in the preceding code, the mode constants are not technically necessary. Strictly speaking, we could accomplish the same thing with literal numeric values (magic values). However, the constants make the purpose of the numeric values much easier to understand. For comparison, the following code shows the 82 | Chapter 4: Static Variables and Static Methods AlarmClock class implemented without constants. Notice that, without reading the code comments, the meaning of the three mode values cannot easily be determined. public class AlarmClock { private var mode = 2; private function signalAlarm ( ) { if (mode == 1) { // Display icon } else if (mode == 2) { // Play sound } else if (mode == 3) { // Display icon and play sound } } } Now let’s move on to the counterpart of static variables: static methods. Static Methods In the preceding section we learned that static variables are used to track information that relates to an entire class. Similarly static methods define functionality that relate to an entire class, not just an instance of that class. For example, the Flash runtime API includes a class named Point that represents a Cartesian point with an x-coordinate and a y-coordinate. The Point class defines a static method, polar( ), which generates a Point object based on a given polar point (i.e., a distance and an angle). Conceptually, converting a polar point to a Cartesian point is a general service that relates to Carte- sian points in general, not to a specific Point object. Therefore, it is defined as a static method. Like instance methods, static methods are created using function definitions within class definitions, but static method definitions must also include the static attribute, as shown in the following generalized code: class SomeClass { static function methodName (identifier1 = value1, identifier2 = value2, ... identifiern = valuen) { } } As with instance methods, access-control modifiers can control the accessibility of static methods in a program. The access-control modifiers available for static-meth- ods definitions are identical to those available for instance-method definitions— namely: public, internal, protected, and private. When no modifier is specified, internal (package-wide access) is used. When a modifier is specified, it is typically placed before the static attribute, as shown in the following code: Static Methods | 83 class SomeClass { public static function methodName (identifier1 = value1, identifier2 = value2, ... identifiern = valuen) { } } To invoke a static method, we use the following general code: SomeClass.methodName(value1, value2,...valuen) In the preceding code, SomeClass is the class within which the static method is defined, methodName is the name of the method, and value1, value2,...valuen is a list of zero or more method arguments. Within the class that defines the method, methodName can be used on its own (without the leading class name and dot). For example, in a class, A, that defines a static method m, the expression A.m( ) is identi- cal to the expression m( ). Nevertheless, to distinguish static methods from instance methods, many developers (and this book) include the leading class name even when it is not strictly required. Some classes exist solely to define static methods. Such classes group related func- tionality together, but objects of the class are never instantiated. For example, the built-in Mouse class exists solely to define the static methods show( ) and hide( ) (used to make the system pointer visible or invisible). Those static methods are accessed through Mouse directly (as in, Mouse.hide( )), not through an instance of the Mouse class. Objects of the mouse class are never created. Static methods have two limitations that instance methods do not. First, a class method cannot use the this keyword. Second, a static method cannot access the instance variables and instance methods of the class in which it is defined (unlike instance methods, which can access static variables and static methods in addition to instance variables and other instance methods). In general, static methods are used less frequently than static variables. Our virtual zoo program does not use static methods at all. To demonstrate the use of static methods, let’s return to the email validation scenario presented earlier in Chapter 2. In that scenario, we created a loop to detect whether or not an email address con- tains the @ character. Now let’s imagine that our application has grown large enough to warrant the creation of a utility class for working with strings. We’ll call the util- ity class StringUtils. The StringUtils class is not meant to be used to create objects; instead, it is merely a collection of static methods. As an example, we’ll define one static method, contains( ), which returns a Boolean value indicating whether a speci- fied string contains a specified character. Here’s the code: public class StringUtils { public function contains (string, character) { for (var i:int = 0; i <= string.length; i++) { if (string.charAt(i) == character) { return true; 84 | Chapter 4: Static Variables and Static Methods } } return false; } } The following code shows how our application would use the contains( ) method to check whether an email address contains the @ character: StringUtils.contains("me@moock.org", "@"); Of course, in a real application, the email address would be supplied by the user and then contains( ) would determine whether or not to submit a form. The following code demonstrates a more realistic situation: if (StringUtils.contains(userEmail, "@")) { // Code here would submit the form } else { // Code here would display an "Invalid data" message to the user } In addition to the static methods we create ourselves, ActionScript automatically cre- ates one static method, known as the class initializer, for every class. Let’s take a look. The Class Initializer When ActionScript defines a class at runtime, it automatically creates a method named the class initializer and executes that method. In this class initializer, Action- Script places all of the class’s static variable initializers and all class-level code that is not a variable definition or a method definition. The class initializer offers an opportunity to perform one-time setup tasks when a class is defined, perhaps by invoking methods or accessing variables that are exter- nal to the current class. For example, suppose we’re creating an email reader applica- tion, and we want its visual appearance to match the operating system’s graphical style. To determine which graphical theme the mail reader should use, the applica- tion’s main class, MailReader, checks the current operating system in its class initial- izer and sets a corresponding static variable, theme. The theme variable dictates the graphical theme used throughout the application. The following code shows the class initializer for MailReader. To check the operating system, MailReader uses the static variable, os, defined by the built-in flash.system.Capabilities class. package { import flash.system.*; public class MailReader { static var theme; if (Capabilities.os == "MacOS") { theme = "MAC"; } else if (Capabilities.os == "Linux") { Class Objects | 85 theme = "LINUX"; } else { theme = "WINDOWS"; } } } Code in the class initializer runs in interpreted mode, and is not compiled by the JIT compiler. Because JIT-compiled code generally executes much more quickly than interpreted code, you should consider moving processor-intensive code out of the class initializer when performance is a priority. Class Objects Earlier we learned that each static method and static variable is accessed through the class that defines it. For example, to access the static variable maxCalories, which is defined by the VirtualPet class, we use the following code: VirtualPet.maxCalories In the preceding code, the use of the class name VirtualPet is not merely a matter of syntax; VirtualPet actually refers to an object that defines the variable maxCalories. The object referenced by VirtualPet is an automatically created instance of the built- in Class class. Every class in ActionScript is represented at runtime by an instance of the Class class. From a programmer’s perspective, Class objects are used primarily to access the static variables and static methods of a class. However, like other objects, Class objects are values that can be assigned to variables, and passed to or returned from methods and functions. For example, the following revised version of our VirtualZoo class assigns the Class object representing the VirtualPet class to a variable, vp, and then uses that variable to create a VirtualPet object: package zoo { public class VirtualZoo { private var pet; public function VirtualZoo ( ) { var vp = VirtualPet; pet = new vp("Stan"); } } } The preceding technique is used when one .swf file wishes to access another .swf file’s classes, and when embedding external assets (such as images or fonts) in a .swf file. We’ll study both of those scenarios in Part II of this book. 86 | Chapter 4: Static Variables and Static Methods We’ve now finished our study of static variables and static methods. Before we move on to the next chapter, let’s compare some of the terms we’ve learned with those used in C++ and Java. C++ and Java Terminology Comparison The concepts of instance variables, instance methods, static variables, and static methods are found in most object-oriented languages. For comparison, Table 4-1 lists the equivalent terms used by Java and C++. On to Functions We’ve learned that an instance method defines a behavior related to a given object and a static method defines a behavior related to a given class. In the next chapter, we’ll study functions, which define standalone behaviors that are not related to any object or class. Table 4-1. Terminology comparison ActionScript Java C++ instance variable field or instance variable data member instance method method member function static variable class variable static data member static method class method static member function 87 Chapter 5 CHAPTER 5 Functions6 A function, or more specifically a function closure, is a discrete set of instructions that carry out some task, independent of any class or object. Function closures have the same basic syntaxand usage as instance methods and static methods; they are defined with the function keyword, can define local variables, are invoked with the parentheses operator, and can optionally return a value. However, unlike instance methods (which are always associated with an object) and static methods (which are always associated with a class), function closures are created and used in standalone form, either as a subtask in a method, or a general utility available throughout a package or an entire program. In the strict technical jargon of the ActionScript 3.0 specification, func- tion closures and methods are both considered types of functions, where the term function refers generally to a callable object represent- ing a set of instructions. Thus, a function closure is a function that is not associated with an object or a class, while a method is a function that is associated with an object (in the case of instance methods) or a class (in the case of static methods). However, in common discussion and most documentation, the term function closure is shortened to function. Unless you are reading the ActionScript 3.0 specification or a text that specifically states otherwise, you can safely assume that func- tion means function closure. In the remainder of this book, the term function means function closure, except where stated otherwise. To create a function, we use the following generalized code in one of the following locations: inside a method, directly inside a package definition, directly outside a package definition, or within another function. Notice that the code used to define a function is identical to the code used to define a basic instance method. In fact, when the following code appears directly within a class body, it creates an instance method, not a function. function identifier (param1, param2, ...paramn) { } 88 | Chapter 5: Functions In the preceding code, identifier is the name of the function and param1, param2, ...paramn is an optional list of the function’s parameters, which are used exactly like the method parameters described in Chapter 1. The curly braces ({}) following the parameter list define the beginning and end of the function body, which con- tains the statements executed when the function is called. To invoke a function we use the following generalized code: theFunction(value1, value2, ... valuen) In the preceding code, theFunction is a reference to the function being invoked and value1, value2,...valuen is a list of arguments that are assigned, in order, to theFunction’s parameters. Package-Level Functions To create a function that is available throughout a package or an entire program, we place a function definition directly within a package body. To make the function accessible within the package that contains its definition only, we precede the defini- tion with the access-control modifier internal, as shown in the following code: package packageName { internal function identifier ( ) { } } To make the function accessible throughout the entire program, we precede the defi- nition with the access control modifier public, as shown in the following code: package packageName { public function identifier ( ) { } } If no access-control modifier is specified, ActionScript automatically uses internal. Adobe’s compilers place two requirements on ActionScript source files (.as files) that affect package-level functions: • Every ActionScript source file (.as file) must have exactly one externally visible definition, which is a class, variable, function, interface, or namespace that is defined as either internal or public within a package statement. • An ActionScript source file’s name must match the name of its sole externally visible definition. Hence, while in theory, ActionScript does not place any limitations on package-level functions, in practice, Adobe’s compilers require each package-level function to be defined as either internal or public in a separate .as file with a matching file name. For more information on compiler limitations, see the section “Compiler Restrictions” in Chapter 7. Package-Level Functions | 89 The following code creates a package-level function, isMac( ), that returns a Boolean value indicating whether or not the current operating system is Macintosh OS. Because the isMac( ) function is defined with the access-control modifier internal,it is accessible within the utilities package only. As discussed in the preceding note, when compiled with an Adobe compiler, the following code would be placed in a separate .as file named isMac.as. package utilities { import flash.system.*; internal function isMac ( ) { return Capabilities.os == "MacOS"; } } To make isMac( ) accessible outside the utilities package, we would change internal to public, as follows: package utilities { import flash.system.*; public function isMac ( ) { return Capabilities.os == "MacOS"; } } However, to use isMac( ) outside the utilities package, we must first import it. For example, suppose isMac( ) is part of a larger program with a class named Welcome in a package named setup. To use isMac( ) in Welcome, Welcome’s source file would import utilities.isMac( ), as follows: package setup { // Import isMac( ) so it can be used within this package body import utilities.isMac; public class Welcome { public function Welcome ( ) { // Use isMac( ) if (isMac( )) { // Do something Macintosh-specific } } } } Global Functions Functions defined at the package-level within the unnamed package are known as global functions because they can be referenced globally, throughout a program, without the need for the import statement. For example, the following code defines a 90 | Chapter 5: Functions global function, isLinux( ). Because the isLinux( ) function is defined within the unnamed package, it is accessible by any code in the same program. package { import flash.system.*; public function isLinux ( ) { return Capabilities.os == "Linux"; } } The following code revises the preceding section’s Welcome class to use isLinux( ) instead of isMac( ). Notice that isLinux( ) need not be imported before being used. package setup { public class Welcome { public function Welcome ( ) { // Use isLinux( ) if (isLinux( )) { // Do something Linux-specific } } } } Many package-level functions and global functions come built-in to each Flash run- time. For a list of available functions, see Adobe’s documentation for the appropri- ate Flash runtime. Perhaps the most useful built-in global function is the trace( ) function, which has the following generalized format: trace(argument1, argument2, argumentn) The trace( ) function is a simple tool for finding errors in a program (i.e., for debug- ging). It outputs the specified arguments to either a window in the ActionScript development environment or to a log file. For example, when running a program in test mode via the Flash authoring tool’s Control ➝ Test Movie command, the output of all trace( ) calls appears in the Output panel. Similarly, when running a program in test mode via FlexBuilder 2’s Run ➝ Debug command, the output of all trace( ) calls appears in the Console. For information on configuring the debugger version of Flash Player to send trace( ) output to a text file, see http://livedocs.macromedia.com/ flex/2/docs/00001531.html. Nested Functions When a function definition occurs within a method or another function, it creates a nested function that is available for use within the containing method or function only. Conceptually, a function nested in a method or function defines a reusable sub- task for the exclusive use of the containing method or function. The following code Source-File-Level Functions | 91 shows a generic example of a nested function, b( ), within an instance method, a( ). The nested function b( ) can be used within the method a( ) only; outside of a( ), b( ) is inaccessible. // Define method a( ) public function a ( ) { // Invoke nested function b( ) b( ); // Define nested function b( ) function b ( ) { // Function body would be inserted here } } In the preceding code, notice that the nested function can be invoked anywhere within the containing method, even before the nested function definition. Referring to a variable or function before it is defined is known as forward referencing. Fur- ther, note that access-control modifiers (public, internal, etc.) cannot be applied to nested functions. The following code shows a more realistic example of a method containing a nested function. The method, getRandomPoint( ) returns a Point object representing a ran- dom point in a supplied rectangle. To produce the random point, the method uses a nested function, getRandomInteger( ), to calculate the random x- and y-coordinate. In getRandomInteger( ), notice the use of the built-in static methods Math.random( ) and Math.floor( ). The Math.random( ) method returns a random floating-point number equal to or greater than 0 but less than 1. The Math.floor( ) method removes the frac- tional portion of a floating-point number. For more information on the static meth- ods of the Math class, see Adobe’s ActionScript Language Reference. public function getRandomPoint (rectangle) { var randomX = getRandomInteger(rectangle.left, rectangle.right); var randomY = getRandomInteger(rectangle.top, rectangle.bottom); return new Point(randomX, randomY); function getRandomInteger (min, max) { return min + Math.floor(Math.random( )*(max+1 - min)); } } Source-File-Level Functions When a function definition occurs at the top-level of a source file, outside any package body, it creates a function that is available within that specific source file only. The following generalized code demonstrates. It shows the contents of a source file, A.as, which contains a package definition, a class definition, and a source-file-level function definition. Because the function is defined outside the 92 | Chapter 5: Functions package statement, it can be used anywhere within A.as, but is inaccessible to code outside of A.as. package { // Ok to use f( ) here class A { // Ok to use f( ) here public function A ( ) { // Ok to use f( ) here } } } // Ok to use f( ) here function f ( ) { } In the preceding code, notice that f( )’s definition does not, and must not, include any access-control modifier (public, internal, etc.). Access-control modifiers cannot be included in source-file-level func- tion definitions. Source-file-level functions are sometimes used to define supporting tasks for a single class (such as class A in the preceding code). However, because private static meth- ods can also define supporting tasks for a class, source-file-level functions are rarely seen in real-world ActionScript programs. Accessing Definitions from Within a Function A function’s location in a program governs its ability to access the program’s defini- tions (e.g., classes, variables, methods, namespaces, interfaces, and other functions). For a complete description of what can and cannot be accessed from code within functions, see the section “Function Scope” in Chapter 16. Note, however, that within a function closure, the this keyword always refers to the global object, no matter where the function is defined. To access the current object within a nested function in an instance method, assign this to a variable, as shown in the following code: public function m ( ) { var currentObject = this; function f ( ) { // Access to currentObject is granted here trace(currentObject); // Displays the object through // which m( ) was invoked } } Function Literal Syntax | 93 Functions as Values In ActionScript, every function is represented by an instance of the Function class. As such, a function can be assigned to a variable, passed to a function, or returned from a function, just like any other value. For example, the following code defines a function, a( ) and then assigns it to the variable b. Notice that the parentheses operator, (),is omitted; if it were included, the code would simply assign a( )’s return value to b. function a ( ) { } var b = a; Once a function has been assigned to a variable, it can be invoked through that vari- able using the standard parentheses operator, (). For example, the following code invokes the function a( ) through the variable b: b( ); Function values are typically used when creating dynamic classes and objects, as dis- cussed in the sections “Dynamically Adding New Behavior to an Instance” and “Using Prototype Objects to Augment Classes” in Chapter 15. Function Literal Syntax As with many of ActionScript’s native classes, instances of the Function class can be created with literal syntax. Function literals have the same syntax as standard func- tion declarations, except that the function name is omitted. The general form is: function (param1, param2, ...paramn) { } where param1, param2, ...paramn is an optional list of parameters. To use the function defined by a function literal outside the expression in which the literal occurs, we can assign it to a variable, as shown in the following code: var someVariable = function (param1, param2, ...paramn) { } Once assigned, the function can then be invoked through that variable, as in: someVariable(arg1, arg2, ...argn) For example, the following code uses a function literal to create a function that squares a number and assigns that function to the variable square: var square = function (n) { return n * n; } To invoke the preceding function, we use the following code: // Squares the number 5 and returns the result square(5) 94 | Chapter 5: Functions Function literals are sometimes used with the built-in function flash.utils.setInterval( ), which takes the following form: setInterval(functionOrMethod, delay) The setInterval( ) function starts an interval, which automatically executes a speci- fied function or method (functionOrMethod) every delay milliseconds. When an interval is created, it is assigned a number, known as an interval ID, that is returned by setInterval( ). The interval ID can be assigned to a variable so that the interval can later be stopped with clearInterval( ), as shown in the following example code: // Start the interval, which invokes doSomething( ) every 50 milliseconds. // Assign the returned interval ID to the variable intervalID. var intervalID = setInterval(doSomething, 50); // ...Some time later in the program, stop invoking doSomething( ) clearInterval(intervalID); For much more sophisticated control over periodic function or method execution, see the Timer class, covered in the sections “Cus- tom Events” in Chapter 12 and “Animating with the TimerEvent. TIMER Event” in Chapter 24. The following code shows a simple class, Clock, which outputs the debug message “Tick!” once per second. Notice the use of the function literal and the built-in func- tions setInterval( ) and trace( ). package { import flash.utils.setInterval; public class Clock { public function Clock ( ) { // Execute the function literal once per second setInterval(function ( ) { trace("Tick!"); }, 1000); } } } Note that function literals are used for the sake of convenience only. The preceding code could easily be rewritten with a nested function, as shown here: package { import flash.utils.setInterval; public class Clock { public function Clock ( ) { // Execute tick( ) once per second setInterval(tick, 1000); function tick ( ):void { Recursive Functions | 95 trace("Tick!"); } } } } The nested-function version of the Clock class is arguably easier to read. Function lit- erals are commonly used when assigning functions to dynamic instance variables, as described in the section “Dynamically Adding New Behavior to an Instance” in Chapter 15. Recursive Functions A recursive function is a function that calls itself. The following code shows a simple example of recursion. Every time trouble( ) runs, it calls itself again: function trouble ( ) { trouble( ); } A recursive function that calls itself unconditionally, as trouble( ) does, causes infi- nite recursion (i.e., a state in which a function never stops calling itself). If left unchecked, infinite recursion would theoretically trap a program in an endless cycle of function execution. To prevent this from happening, practical recursive func- tions call themselves only while a given condition is met. One classic use of recur- sion is to calculate the mathematical factorial of a number, which is the product of all positive integers less than or equal to the number. For example, the factorial of 3 (written as 3! in mathematical nomenclature) is 3 * 2 * 1, which is 6. The factorial of 5is5*4*3*2*1,which is 120. Example 5-1 shows a factorial function that uses recursion. As usual, there is more than one way to skin a proverbial cat. Using a loop, we can also calculate a factorial without recursion, as shown in Example 5-2. Example 5-1. Calculating factorials using recursion function factorial (n) { if (n < 0) { return; // Invalid input, so quit } else if (n <= 1) { return 1; } else { return n * factorial(n-1); } } // Usage: factorial(3); // Returns: 6 factorial(5); // Returns: 120 96 | Chapter 5: Functions Examples 5-1 and 5-2 present two different ways to solve the same problem. The recursive approach says, “The factorial of 6 is 6 multiplied by the factorial of 5. The factorial of 5 is 5 multiplied by the factorial of 4...” and so on. The nonrecursive approach loops over the numbers from 1 to n and multiplies them all together into one big number. Function recursion is considered elegant because it provides a simple solution—call- ing the same function repeatedly—to a complexproblem. However, repeated func- tion calls are less efficient than loop iterations. The nonrecursive approach to calculating factorials is many times more efficient than the recursive approach. The nonrecursive approach also avoids the Flash runtime’s maximum recursion limit, which defaults to 1000 but can be set via the compiler argument default-script- limits. As we’ll see in Chapter 18, recursion is sometimes used to process the hierarchically structured content of XML documents. Using Functions in the Virtual Zoo Program Let’s apply our new knowledge of functions to our virtual zoo program. (For a refresher on our program’s existing code, see the VirtualPet class in Example 4-1 in Chapter 4.) Recall that when we last saw our virtual zoo program, pets had the ability to eat (i.e., gain calories), but not to digest (i.e., lose calories). To give our pets the ability to digest, we’ll add a new method to the VirtualPet class, named digest( ). The digest( ) method will subtract calories from the VirtualPet object on which it is invoked. To simulate digestion over time, we’ll create an interval that invokes digest( ) once per second. The amount of calories consumed at each digest( ) invocation will be deter- mined by a new static variable, caloriesPerSecond. By default, we’ll set caloriesPerSecond to 100, allowing a pet to survive a maximum of 20 seconds on a “full stomach.” The following code shows the caloriesPerSecond variable definition: private static var caloriesPerSecond = 100; Example 5-2. Calculating factorials without recursion function factorial (n) { if (n < 0) { return; // Invalid input, so quit } else { var result = 1; for (var i = 1; i <= n; i++) { result = result * i; } return result; } } Using Functions in the Virtual Zoo Program | 97 The following code shows the digest( ) method. Notice that because digestion is an internal task, digest( ) is declared private. private function digest ( ) { currentCalories -= VirtualPet.caloriesPerSecond; } To create the interval that invokes digest( ) once per second, we use the built-in setInterval( ) function. Each pet should start digesting as soon as it is created, so we’ll put our setInterval( ) call in the VirtualPet constructor method. We’ll also store setInterval( )’s returned interval ID in a new instance variable, digestIntervalID,so that we can stop the interval later if necessary. The following code shows the digestIntervalID variable definition: private var digestIntervalID; This code shows the updated VirtualPet constructor: public function VirtualPet (name) { setName(name); // Call digest( ) once per second digestIntervalID = setInterval(digest, 1000); } Now that a VirtualPet object can digest food, let’s use the global function trace( ) to report each pet’s current status during debugging. We’ll issue a status report every time digest( ) or eat( ) runs. Here’s the updated digest( ) method: private function digest ( ) { currentCalories -= VirtualPet.caloriesPerSecond; trace(getName( ) + " digested some food. It now has " + currentCalories + " calories remaining."); } Here’s the updated eat( ) method: public function eat (numberOfCalories) { var newCurrentCalories = currentCalories + numberOfCalories; if (newCurrentCalories > VirtualPet.maxCalories) { currentCalories = VirtualPet.maxCalories; } else { currentCalories = newCurrentCalories; } trace(getName( ) + " ate some food. It now has " + currentCalories + " calories remaining."); } If we were to run our virtual zoo program now, we would see the following in the Output window (Flash authoring tool) or the Console (Flex Builder): Stan digested some food. It now has 900 calories remaining. Stan digested some food. It now has 800 calories remaining. 98 | Chapter 5: Functions Stan digested some food. It now has 700 calories remaining. Stan digested some food. It now has 600 calories remaining. Stan digested some food. It now has 500 calories remaining. Stan digested some food. It now has 400 calories remaining. Stan digested some food. It now has 300 calories remaining. Stan digested some food. It now has 200 calories remaining. Stan digested some food. It now has 100 calories remaining. Stan digested some food. It now has 0 calories remaining. Stan digested some food. It now has -100 calories remaining. Stan digested some food. It now has -200 calories remaining. Stan digested some food. It now has -300 calories remaining. Oops. Pets shouldn’t be allowed to have negative calorie values. Instead, pets should die when currentCalories reaches 0. In our program, we’ll simulate the state of death in the following ways: •If currentCalories is 0, the program ignores attempts to increase currentCalories when eat( ) is called. • When currentCalories reaches 0, the program stops the interval that calls digest( ) and displays a “pet death” message. First, let’s take care of the update to the eat( ) method. A simple conditional should do the trick; here’s the code: public function eat (numberOfCalories) { // If this pet is dead... if (currentCalories == 0) { // ...quit this method without modifying currentCalories trace(getName( ) + " is dead. You can't feed it."); return; } var newCurrentCalories = currentCalories + numberOfCalories; if (newCurrentCalories > VirtualPet.maxCalories) { currentCalories = VirtualPet.maxCalories; } else { currentCalories = newCurrentCalories; } trace(getName( ) + " ate some food. It now has " + currentCalories + " calories remaining."); } Next, we need to stop calling digest( ) if currentCalories reaches 0. To do so, we’ll use flash.utils.clearInterval( ). Here’s the code: private function digest ( ) { // If digesting more calories would leave the pet's currentCalories at // 0 or less... if (currentCalories - VirtualPet.caloriesPerSecond <= 0) { // ...stop the interval from calling digest( ) clearInterval(digestIntervalID); // Then give the pet an empty stomach currentCalories = 0; // And report the pet's death Using Functions in the Virtual Zoo Program | 99 trace(getName( ) + " has died."); } else { // ...otherwise, digest the stipulated number of calories currentCalories -= VirtualPet.caloriesPerSecond; // And report the pet's new status trace(getName( ) + " digested some food. It now has " + currentCalories + " calories remaining."); } } Example 5-3 shows the complete code for the VirtualPet class, including all the changes we just made. Example 5-3. The VirtualPet class package zoo { import flash.utils.setInterval; import flash.utils.clearInterval; internal class VirtualPet { private static var maxNameLength = 20; private static var maxCalories = 2000; private static var caloriesPerSecond = 100; private var petName; private var currentCalories = VirtualPet.maxCalories/2; private var digestIntervalID; public function VirtualPet (name) { setName(name); digestIntervalID = setInterval(digest, 1000); } public function eat (numberOfCalories) { if (currentCalories == 0) { trace(getName( ) + " is dead. You can't feed it."); return; } var newCurrentCalories = currentCalories + numberOfCalories; if (newCurrentCalories > VirtualPet.maxCalories) { currentCalories = VirtualPet.maxCalories; } else { currentCalories = newCurrentCalories; } trace(getName( ) + " ate some food. It now has " + currentCalories + " calories remaining."); } public function getHunger ( ) { return currentCalories / VirtualPet.maxCalories; } 100 | Chapter 5: Functions Back to Classes We’ve finished our study of functions in ActionScript. In the next chapter, we’ll return to the topic of classes, with a specific focus on using inheritance to create rela- tionships between two or more classes. Once you understand inheritance, we’ll be able to make our virtual zoo program ready to compile and run. public function setName (newName) { // If the proposed new name has more than maxNameLength characters... if (newName.length > VirtualPet.maxNameLength) { // ...truncate it newName = newName.substr(0, VirtualPet.maxNameLength); } else if (newName == "") { // ...otherwise, if the proposed new name is an empty string, // then terminate this method without changing petName return; } // Assign the new, validated name to petName petName = newName; } public function getName ( ) { return petName; } private function digest ( ) { // If digesting more calories would leave the pet's currentCalories at // 0 or less... if (currentCalories - VirtualPet.caloriesPerSecond <= 0) { // ...stop the interval from calling digest( ) clearInterval(digestIntervalID); // Then give the pet an empty stomach currentCalories = 0; // And report the pet's death trace(getName( ) + " has died."); } else { // ...otherwise, digest the stipulated number of calories currentCalories -= VirtualPet.caloriesPerSecond; // And report the pet's new status trace(getName( ) + " digested some food. It now has " + currentCalories + " calories remaining."); } } } } Example 5-3. The VirtualPet class (continued) 101 Chapter 6 CHAPTER 6 Inheritance7 In object-oriented programming, inheritance is a formal relationship between two or more classes, wherein one class borrows (or inherits) the variable and method defini- tions of another class. In the practical, technical sense, inheritance simply lets one class use the code in another class. But the term inheritance implies much more than code reuse. Inheritance is as much an intellectual tool as it is a technical tool. It lets programmers conceptualize a group of classes in hierarchical terms. In biology, inheritance is a genetic process through which one living creature passes on traits to another. You are said to have inherited your mother’s eyes or your father’s nose, even though you don’t look exactly like either of your parents. In object-oriented programming, inheritance has a similar connotation. It lets a class look and feel in many ways like another class, while add- ing its own unique features. This chapter begins by examining the syntax and general use of inheritance. Once we understand inheritance on a practical level, we’ll consider its benefits and alterna- tives. Finally, we’ll apply inheritance to our virtual zoo program. A Primer on Inheritance Let’s consider a very simple, abstract example to get a feel for how inheritance works (we’ll get into practical applications once we cover the basic syntax). Here’s a class named A, with a single instance method, m( ), and a single instance variable, v: public class A { public var v = 10; public function m ( ) { trace("Method m( ) was called"); } } 102 | Chapter 6: Inheritance As usual, we can create an instance of class A, invoke method m( ), and access vari- able v like this: var aInstance = new A( ); aInstance.m(); // Displays: Method m( ) was called trace(aInstance.v); // Displays: 10 Nothing new so far. Now let’s add a second class, B, that inherits method m( ) and variable v from class A. To set up the inheritance relationship between A and B,we use the extends keyword: public class B extends A { // No methods or variables defined } Because class B extends (inherits from) class A, instances of B can automatically use the method m( ) and the variable v (even though class B does not define that method or variable directly): var bInstance:B = new B( ); bInstance.m(); // Displays: Method m( ) was called trace(bInstance.v); // Displays: 10 When bInstance.m( ) is invoked, ActionScript checks to see if class B defines a method named m( ). ActionScript does not find method m( ) defined in class B,soit checks B’s superclass (i.e., the class that B extends), for the method. There, in class A, ActionScript finds m( ) and invokes it on bInstance. Notice that class B does not define any methods or variables of its own. In practice, there isn’t much point in defining a class that doesn’t add anything to the class it extends; therefore, doing so is usually discouraged. Normally, class B would define its own methods and/or variables in addition to inheriting A’s methods and vari- ables. That is, a subclass is really a superset of the features available in its superclass; the subclass has everything available in the superclass and more. Here is a new ver- sion of class B, which inherits method m( ) and variable v from class A, and also defines its own method, n( ): public class B extends A { public function n ( ) { trace("Method n( ) was called"); } } Now instances of B can use all the methods and variables of both B and its super- class, A: var bInstance = new B( ); // Invoke inherited method, defined by class A bInstance.m(); // Displays: Method m( ) was called // Invoke method defined by class B bInstance.n(); // Displays: Method n( ) was called // Access inherited variable trace(bInstance.v); // Displays: 10 A Primer on Inheritance | 103 Class B is said to specialize class A. It uses the features of class A as a base on which to build, adding its own features or even—as we’ll see later—overriding A’s features with versions modified for its own needs. Accordingly, in an inheritance relationship between two classes, the extended class (in our case, class A) is called the base class, and the class that does the extending (in our case, class B) is called the derived class. The terms ancestor and descendant are also sometimes used to refer to the base class and the derived class, respectively. Inheritance can (and often does) involve many more than two classes. For example, even though class B inherits from class A, class B can act as a base class for another class. The following code shows a third class, C, that extends class B and also defines a new method, o( ). Class C can use all the methods and variables defined by itself or by any of its ancestors—that is, its superclass (B), or its superclass’s superclass (A). public class C extends B { public function o ( ) { trace("Method o( ) was called"); } } // Usage: var cInstance = new C( ); // Invoke method inherited from A cInstance.m(); // Displays: Method m( ) was called // Invoke method inherited from B cInstance.n(); // Displays: Method n( ) was called // Invoke method defined by C cInstance.o(); // Displays: Method o( ) was called // Access variable inherited from A. trace(cInstance.v); // Displays: 10 Furthermore, a single superclass can have any number of subclasses (however, a superclass has no way of knowing which subclasses extend it). The following code adds a fourth class, D, to our example. Like class B, class D inherits directly from class A. Class D can use the methods and variables defined by itself and by its super- class, A. public class D extends A { public function p ( ) { trace("Method p( ) was called"); } } With four classes now in our example, we’ve built up what’s known as an inheritance tree or class hierarchy. Figure 6-1 shows that hierarchy visually. Note that a single subclass can’t have more than one direct superclass. All OOP applications can be depicted with a class diagram such as the one shown in Figure 6-1. In fact, many developers start their work by creating a class diagram before writing any code. Class diagrams can be informal—drawn according to a developer’s personal iconography—or formal, drawn according to a diagramming specification such as Unified Modeling Language (UML) (see http://www.uml.org). 104 | Chapter 6: Inheritance Just as we design our own class hierarchies for our OOP applications, ActionScript also organizes its built-in classes according to a hierarchy. In fact, every class in ActionScript (both built-in and custom) inherits directly or indirectly from the root of the built-in hierarchy: Object. The Object class defines some very basic methods and variables that all classes can use, through inheritance. For example, any class can use the Object.toString( ) method, which returns a string representation of an object. Static Methods and Static Variables Not Inherited Unlike instance methods and instance variables, a subclass does not inherit its super- class’s static methods and static variables. For example, in the following code, we define a static method, s( ), in the class A. The method s( ) is not inherited by A’s subclass, B, and, therefore, cannot be accessed as B.s( ). public class A { public static function s ( ) { trace("A.s( ) was called"); } } public class B extends A { public function B ( ) { B.s(); // Error! Illegal attempt to access A.s( ) through B } } However, within the body of either A or B, static methods and static variables defined by A can be referred to directly without the class name, as in s( ) rather than A.s( ). Nevertheless, it’s generally wise to include the class name when referring to static methods or static variables. When the class name is included, the origin of the method or variable is clear. Figure 6-1. A class hierarchy v m() A n() B p() D o() C Overriding Instance Methods | 105 Overriding Instance Methods In our study of inheritance so far, we’ve covered reuse, in which a subclass uses its superclass’s methods and variables, and we’ve covered extension, in which a sub- class adds its own methods and variables. We’ll now turn to redefinition, in which a subclass provides an alternative version of a method defined by its superclass. (Bear in mind that reuse, extension, and redefinition are not mutually exclusive. A sub- class might employ all three techniques.) Redefinition lets us customize an existing class for a specific purpose by augmenting, constraining, or even nullifying one or more of its original behaviors. Redefining a method is known technically as overriding that method. ActionScript 3.0 allows instance methods to be redefined but not instance variables, static variables, or static methods. To override a superclass’s instance method, we supply an instance method defini- tion of the same name in the subclass, and precede that definition with the keyword override. For example, consider the following code, which creates a class, A, with an instance method, m( ): public class A { // Declare an instance method in the superclass public function m ( ) { trace("A's m( ) was called"); } } Also consider the following code, which creates a class, B, that inherits from A: // Class B is a subclass of class A public class B extends A { } To override m( ) in B, we use the following code: public class B extends A { // Override the superclass's method m( ) override public function m ( ) { trace("B's m( ) was called"); } } Notice that B’s version of m( ) has not only the same name, but also the same access- control modifier (i.e., public) as A’s version. An override attempt succeeds only if the overriding version of the method has the same name, access-control modifier, parameter list, and return type as the method being overridden. (We’ll learn about return types in Chapter 8.) Otherwise, an error occurs. 106 | Chapter 6: Inheritance When m( ) is invoked on an instance of class A, ActionScript uses A’s definition of the method. But when m( ) is invoked on an instance of class B, ActionScript uses B’s definition of the method instead of class A’s definition: var aInstance = new A( ); aInstance.m(); // Displays: A's m( ) was called var bInstance = new B( ); bInstance.m(); // Displays: B's m( ) was called Let’s consider a more realistic example. Suppose we’re building a geometry program that depicts rectangles and squares. To handle the rectangles, we create a Rectangle class, as follows: public class Rectangle { protected var w = 0; protected var h = 0; public function setSize (newW, newH) { w = newW; h = newH; } public function getArea ( ) { return w * h; } } To handle squares, we could create a completely unrelated Square class. But a square is really just a rectangle with sides of equal width and height. To exploit that similar- ity, we’ll create a Square class that extends Rectangle but alters the setSize( ) method to prevent w and h from being set unless newW equals newH. The constraint applies only to squares, not to rectangles in general, so it doesn’t belong in the Rectangle class. Here’s the Square class, showing the overridden setSize( ) method: public class Square extends Rectangle { override public function setSize (newW, newH) { // Here's the constraint introduced by the Square class if (newW == newH) { w = newW; h = newH; } } } When setSize( ) is invoked on a Square or Rectangle instance, ActionScript uses the version of the method that matches the actual class of the instance. For example, in the following code, we invoke setSize( ) on a Rectangle instance. ActionScript knows that the instance’s class is Rectangle, so it invokes Rectangle’s version of setSize( ): var r = new Rectangle( ); r.setSize(4,5); trace(r.getArea( )); // Displays: 20 Overriding Instance Methods | 107 By contrast, in the following code, we invoke setSize( ) on a Square instance. This time ActionScript knows that the instance’s class is Square, so it invokes Square’s version of setSize( ), not Rectangle’s version of setSize( ): var s = new Square( ); s.setSize(4,5); trace (s.getArea()); // Displays: 0 (The setSize( ) method prevented the // illegal variable assignment.) In the preceding code, the output of s.getArea( )—0—indicates that values of w and h were not set by the call to s.setSize( ); Square’s version of setSize( ) sets w and h only when newW and newH are equal. Invoking an Overridden Instance Method When a subclass overrides an instance method, the superclass’s version of the method is not lost. It remains accessible to instances of the subclass via the super operator, which can invoke an overridden method as follows: super.methodName(arg1, arg2, ...argn); In the preceding code, methodName is the name of the overridden method to invoke, and arg1, arg2, ... argn are the arguments to pass to that method. (We’ll discuss other uses of super later in this chapter.) As an example of invoking an overridden method, let’s return to the Square and Rectangle scenario. In the previous section, our Square.setSize( ) method needlessly duplicated the code in the Rectangle.setSize( ) method. The Rectangle version was: public function setSize (newW, newH) { w = newW; h = newH; } The Square version of setSize( ) merely added an if statement: override public function setSize (newW, newH) { if (newW == newH) { w = newW; h = newH; } } To avoid the duplication of setting w and h in both methods, we can use super,as shown in this revised version of Square.setSize( ): override public function setSize (newW, newH) { if (newW == newH) { // Invoke the superclass's setSize( ) method, on the current instance super.setSize(newW, newH); } } 108 | Chapter 6: Inheritance The Square class’s revised setSize( ) method checks if newW and newH are equal; if they are, it invokes Rectangle’s setSize( ) on the current instance. The Rectangle class’s setSize( ) method takes care of setting w and h. The setSize( ) method example shows how a subclass can override a method to con- strain its behavior. A subclass can also override a method to augment its behavior. For example, the following code creates ScreenRectangle, a subclass of Rectangle that draws a rectangle to the screen. The ScreenRectangle subclass overrides the setSize( ) method, retaining the behavior of the overridden method, but adding a call to draw( ), so the rectangle changes size on screen whenever setSize( ) is invoked. Here’s the code: public class ScreenRectangle extends Rectangle { override public function setSize (newW, newH) { // Call Rectangle's version of setSize( ) super.setSize(newW, newH); // Now render the rectangle on screen draw( ); } public function draw ( ) { // Screen-rendering code goes here } } Overriding can also be used to nullify the behavior of a method. The technique is straightforward: the subclass’s version of the overridden method simply does noth- ing. For example, the following code shows a subclass named ReadOnlyRectangle that disables the Rectangle class’s setSize( ) method, preventing an instance from changing size: public class ReadOnlyRectangle extends Rectangle { // This effectively disables the setSize( ) method // for instances of the ReadOnlyRectangle class. override public function setSize (newW, newH) { // Do nothing } } Constructor Methods in Subclasses Now that we’ve studied the behavior of instance methods and instance variables in relation to inheritance, let’s turn our attention to constructor methods. Recall that a constructor method initializes the instances of a class by: • Calling methods that perform setup tasks • Setting variables on the object being created Constructor Methods in Subclasses | 109 When a class is extended, the subclass can define a constructor method of its own. A subclass constructor is expected to: • Perform setup tasks related to the subclass • Set variables defined by the subclass • Invoke the superclass constructor method (sometimes called the superconstructor) A subclass constructor method, if provided, is required to invoke its superclass con- structor, via the keyword super. Furthermore, the superclass constructor invocation must occur before any instance variable or instance method is accessed. If no such invocation is provided, the compiler adds a no-argument superclass constructor call automatically. Finally, super must not be used twice in a constructor method. Forbidding the use of super after any instance variable or instance method is accessed has the following benefits: • Prevents methods from being called on an object that has not yet been initialized • Prevents variable access on an object that has not yet been initialized • Prevents variable assignments in the superclass constructor from overwriting variable assignments in the subclass constructor Don’t confuse the two forms of the super operator. The first form, super( ), invokes a superclass’s constructor method. The second form, super.methodName( ), invokes a superclass’s method. The first form is allowed in a constructor method only. The second form is allowed anywhere in a constructor method or instance method, and can be used multiple times. Let’s try using super to invoke a superclass’s constructor method in a simplified situ- ation. The following code defines a class, A, with an empty constructor method: public class A { public function A ( ) { } } The following code defines a class, B, which extends A. Within B’s constructor method, we use super to invoke A’s constructor method: public class B extends A { // Subclass constructor public function B ( ) { // Invoke superclass's constructor method super( ); } } 110 | Chapter 6: Inheritance The following two constructor method definitions are functionally synonymous. In the first case, we call the superclass’s constructor method explicitly; in the second case, ActionScript calls the superclass’s constructor method implicitly: public function B ( ) { // Invoke superclass's constructor method explicitly super( ); } public function B ( ) { // No constructor call. ActionScript provides one implicitly } If a subclass does not define a constructor method at all, ActionScript automatically creates one and adds a super call as its only statement. The following two definitions of the class B are functionally identical; the first is an explicit version of what the compiler creates automatically in the second: // Explicitly provide constructor public class B extends A { // Declare a constructor explicitly public function B ( ) { // Invoke superclass's constructor method explicitly super( ); } } // Let compiler create default constructor automatically public class B extends A { } A subclass constructor method can (and often does) define different parameters than its superclass counterpart. For example, our Rectangle class might define a construc- tor with width and height parameters. And our Square subclass might provide its own constructor that defines a single side parameter (squares have the same width and height, so specifying both is redundant). Example 6-1 shows the code. Example 6-1. The Rectangle and Square constructors public class Rectangle { protected var w = 0; protected var h = 0; // Rectangle constructor public function Rectangle (width, height) { setSize(width, height); } public function setSize (newW, newH) { w = newW; h = newH; } Constructor Methods in Subclasses | 111 Incidentally, you might wonder whether the Square class’s setSize( ) method is better off defining a single side parameter rather than separate width and height parame- ters. The following version of setSize( ) demonstrates (notice that the method no longer needs to check whether newW equals newH): override public function setSize (side) { // Invoke the superclass's setSize( ) method, on the current instance super.setSize(side, side); } While the preceding version of setSize( ) is definitely more appropriate for a Square class, it would cause an error because it defines fewer parameters than the Rectangle class’s version of setSize( ) (remember that the number of parameters defined by an overriding method must match that of the overridden method). Later, under “Inher- itance Versus Composition,” we’ll consider an alternate, legal approach for imple- menting the single-parameter version of setSize( ) in the Square class. When defining a subclass’s constructor method, be sure to supply all required argu- ments to the superclass’s constructor. In the following example, the ColoredBall class erroneously defines a constructor method that doesn’t supply necessary information to its superclass’s constructor method: public class Ball { private var r; public function Ball (radius) { r = radius; } } public class ColoredBall extends Ball { private var c; public function getArea ( ) { return w * h; } } public class Square extends Rectangle { // Square constructor public function Square (side) { // Pass the side parameter onto the Rectangle constructor super(side, side); } override public function setSize (newW, newH) { if (newW == newH) { // Invoke the superclass's setSize( ) method, on the current instance super.setSize(newW, newH); } } } Example 6-1. The Rectangle and Square constructors (continued) 112 | Chapter 6: Inheritance // Here's the problematic constructor... public function ColoredBall (color) { // OOPs! No call to super( ). An error will occur here because // Ball's constructor requires an argument for the radius parameter c = color; } } Here’s the corrected version of ColoredBall, which supplies the necessary argument to the Ball constructor’s behavior: public class ColoredBall extends Ball { private var c; // All fixed up... public function ColoredBall (radius, color) { super(radius); c = color; } } Notice that, as a matter of good form, the subclass constructor lists the superclass’s constructor parameters first (in this case, radius), then the additional subclass con- structor arguments (in this case, color). Preventing Classes from Being Extended and Methods from Being Overridden To prevent a class from being extended or a method from being overridden, we pre- cede that class or method definition with the final attribute. For example, the follow- ing code defines a class, A, that cannot be extended: final public class A { } Because class A is defined with the final attribute, the following attempt to extend A: public class B extends A { } yields this compile-time error: Base class is final. Likewise, the following code defines a method, m( ), that cannot be overridden: public class A { final public function m ( ) { } } Subclassing Built-in Classes | 113 Because class m( ) is defined with the final attribute, the following attempt to over- ride m( ): public class B extends A { override public function m ( ) { } } yields this compile-time error: Cannot redefine a final method. The final attribute is used for two reasons in ActionScript: • In some situations, final methods execute faster than non-final methods. If you are looking to improve your application’s performance in every possible way, try making its methods final. Note, however, that in future Flash runtimes, Adobe expects non-final methods to execute as quickly as final methods. • Methods that are final help hide a class’s internal details. Making a class or a method final prevents other programmers from extending the class or overriding the method for the purpose of examining the class’s internal structure. Such pre- vention is considered one of the ways to safeguard an application from being maliciously exploited. Efficiency and safeguarding aside, the programming community is divided on whether making methods and classes final is good object-oriented programming practice. On one hand, some argue that final methods and classes are useful because they allow a programmer to guarantee that an object has an intended behavior, rather than an unexpected (and potentially problematic) overridden behavior. On the other hand, others argue that final methods and classes contradict the general object-oriented principle of polymorphism, wherein an instance of a subclass can be used anywhere an instance of its superclass is expected. We’ll learn more about poly- morphism later in this chapter. Subclassing Built-in Classes Just as we can create subclasses of our own custom classes, we can also create sub- classes of any non-final built-in class, allowing us to implement specialized function- ality based on an existing ActionScript class. For an example of extending the built- in Array class, see Programming ActionScript 3.0 ➝ Core ActionScript 3.0 Data Types and Classes ➝ Working with Arrays ➝ Advanced Topics, in Adobe’s Program- ming ActionScript 3.0. For an example of extending the built-in Flash runtime class, Shape, see the section “Custom Graphical Classes” in Chapter 20. Some built-in ActionScript classes are simply collections of class methods and class variables—for example, the Math, Keyboard, and Mouse classes exist merely to store related methods and variables (e.g., Math.random( ) and Keyboard.ENTER). Such classes are known as static method libraries, and are typically declared final. Rather than 114 | Chapter 6: Inheritance extending these classes, you must distribute your own static method libraries sepa- rately. For example, rather than adding a factorial( ) method to a subclass of the Math class, you would create a custom class, say AdvancedMath, to hold your factorial( ) method. The AdvancedMath class cannot be related to the Math class via inheritance. The Theory of Inheritance So far this chapter has focused mainly on the practical details of using inheritance in ActionScript. But the theory of why and when to use inheritance runs much deeper than the technical implementation. Before we conclude, let’s consider some basic theoretical principles, bearing in mind that a few pages is hardly enough room to do the topic justice. For a much more thorough consideration of inheritance theory, see “Using Inheritance Well” (http://archive.eiffel.com/doc/manuals/technology/oosc/ inheritance-design/page.html), an online excerpt from Bertrand Meyer’s illuminating work Object-Oriented Software Construction (Prentice Hall). Why Inheritance? Superficially, the obvious benefit of inheritance is code reuse. Inheritance lets us sep- arate a core feature set from customized versions of that feature set. Code for the core is stored in a superclass while code for the customizations is kept neatly in a subclass. Furthermore, more than one subclass can extend the superclass, allowing multiple customized versions of a particular feature set to exist simultaneously. If the implementation of a feature in the superclass changes, all subclasses automatically inherit the change. But inheritance also lets us express the architecture of an application in hierarchical terms that mirror the real world and the human psyche. For example, in the real world, we consider plants different from animals, but we categorize both as living things. We consider cars different from planes, but we see both as vehicles. Corre- spondingly, in a human resources application, we might have an Employee super- class with Manager, CEO, and Worker subclasses. Or, in a banking application, we might create a BankAccount superclass with CheckingAccount and SavingsAccount subclasses. These are canonical examples of one variety of inheritance sometimes called subtype inheritance, in which the application’s class hierarchy is designed to model a real-world situation (a.k.a. the domain or problem domain). However, while the Employee and BankAccount examples make attractive demonstra- tions of inheritance, not all inheritance reflects the real world. In fact, overemphasiz- ing real-world modeling can lead to miscomprehension of inheritance and its subsequent misuse. For example, given a Person class, we might be tempted to create Female and Male subclasses. These are logical categories in the real world, but if the application using those classes were, say, a school’s reporting system, we’d be forced to create MaleStudent and FemaleStudent classes just to preserve the real-world hierar- chy. In our program, male students do not define any operations differently from The Theory of Inheritance | 115 female students and, therefore, should be used identically. The real-world hierarchy in this case conflicts with our application’s hierarchy. If we need gender information, we’re better off creating a single Student class and adding a gender variable to the Person class. As tempting as it may be, we should avoid creating inheritance struc- tures based solely on the real world rather than the needs of our program. Finally, in addition to code reuse and logical hierarchy, inheritance allows types of objects to be used where a single type is expected. Known as polymorphism, this important benefit warrants a discussion all its own. Polymorphism and Dynamic Binding Polymorphism is a feature of all truly object-oriented languages, wherein an instance of a subclass can be used anywhere an instance of its superclass is expected. The word polymorphism itself means literally “many forms”—each single object can be treated as an instance of its own class or as an instance of any of its superclasses. Polymorphism’s partner is dynamic binding, which guarantees that a method invoked on an object will trigger the behavior defined by that object’s actual class. The canonical example of polymorphism and dynamic binding is a graphics applica- tion that displays shapes. The application defines a Shape class with an unimple- mented draw( ) method: public class Shape { public function draw ( ) { // No implementation. In some other languages, draw( ) would be // declared with the abstract attribute, which syntactically // forces subclasses of Shape to provide an implementation. } } The Shape class has several subclasses—a Circle class, a Rectangle class, and a Triangle class, each of which provides its own definition for the draw( ) method: public class Circle extends Shape { override public function draw ( ) { // Code to draw a Circle on screen, not shown... } } public class Rectangle extends Shape { override public function draw ( ) { // Code to draw a Rectangle on screen, not shown... } } public class Triangle extends Shape { override public function draw ( ) { // Code to draw a Triangle on screen, not shown... } } 116 | Chapter 6: Inheritance To add a new shape to the screen, we pass a Circle, Rectangle,orTriangle instance to the addShape( ) method of the application’s main class, DrawingApp. Here’s DrawingApp’s addShape( ) method: public function addShape (newShape) { newShape.draw( ); // Remainder of method (code not shown) would add the new shape // to an internal list of shapes on screen } Here’s how we add a Circle shape to the screen: drawingApp.addShape(new Circle( )); The addShape( ) method invokes the new shape’s draw( ) method and adds the new shape to an internal list of shapes on screen. And here’s the key point—addShape( ) invokes draw( ) without knowing (or caring) whether the new shape is a Circle, Rectangle,orTriangle instance. Through the runtime process of dynamic binding, ActionScript uses the appropriate implementation of that method. That is, if the instance is a Circle, ActionScript invokes Circle.draw( ); if it’s a Rectangle, Action- Script invokes Rectangle.draw( ); and if it’s a Triangle, ActionScript invokes Triangle.draw( ). Importantly, the specific class of the new shape is not known at compile time. Hence, dynamic binding is often called late binding: the method call is bound to a particular implementation “late” (i.e., at runtime). The key benefit of dynamic binding and polymorphism is containment of changes to code. Polymorphism lets one part of an application remain fixed even when another changes. For example, let’s consider how we’d handle the drawing of shapes if poly- morphism didn’t exist. First, we’d have to use unique names for each version of draw( ): public class Circle extends Shape { public function drawCircle ( ) { // Code to draw a Circle on screen, not shown... } } public class Rectangle extends Shape { public function drawRectangle ( ) { // Code to draw a Rectangle on screen, not shown... } } public class Triangle extends Shape { public function drawTriangle ( ) { // Code to draw a Triangle on screen, not shown... } } Then, within DrawingApp’s addShape( ) method we’d have to use the is operator to check the class of each new shape manually and invoke the appropriate draw The Theory of Inheritance | 117 method, as shown in the following code. An is operation returns the value true if the specified expression belongs to the specified datatype; otherwise, it returns false. We’ll study datatypes and the is operator in Chapter 8. public function addShape (newShape) { if (newShape is Circle) { newShape.drawCircle( ); } else if (newShape is Rectangle) { newShape.drawRectangle( ); } else if (newShape is Triangle) { newShape.drawTriangle( ); } // Remainder of method (code not shown) would add the new shape // to an internal list of shapes on screen } That’s already more work. But imagine what would happen if we added 20 new kinds of shapes. For each one, we’d have to update the addShape( ) method. In a polymorphic world, we don’t have to touch the code that invokes draw( ) on each Shape instance. As long as each Shape subclass supplies its own valid definition for draw( ), our application will “just work” without other changes. Polymorphism not only lets programmers collaborate more easily, but it allows them to use and expand on a code library without requiring access to the library’s source code. Some argue that polymorphism is object-oriented programming’s greatest con- tribution to computer science. Inheritance Versus Composition In this chapter, we’ve focused most of our attention on one type of inter-object rela- tionship: inheritance. But inheritance isn’t the only game in town. Composition,an alternative form of inter-object relationship, often rivals inheritance as an object-ori- ented design technique. In composition, one class (the front-end class) stores an instance of another class (the back-end class) in an instance variable. The front-end class delegates work to the back-end class by invoking methods on that instance. Here’s the basic approach, shown in generalized code: // The back end class is analogous to the superclass in inheritance public class BackEnd { public function doSomething ( ) { } } // The front end class is analogous to the subclass in inheritance public class FrontEnd { // An instance of the back end class is stored in // a private instance variable, in this case called be private var be; 118 | Chapter 6: Inheritance // The constructor creates the instance of the back end class public function FrontEnd ( ) { be = new BackEnd( ); } // This method delegates work to BackEnd's doSomething( ) method public function doSomething ( ) { be.doSomething( ); } } Notice that the FrontEnd class does not extend the BackEnd class. Composition does not require or use its own special syntax, as inheritance does. Furthermore, the front- end class might use a subset of the methods of the back end class, or it might use all of them, or it might add its own unrelated methods. The method names in the front- end class might match those exactly in the back-end class, or they might be com- pletely different. The front-end class can constrain, extend, or redefine the back-end class’s features, just like a subclass in inheritance. Earlier we learned how, using inheritance, a Square class could constrain the behav- ior of a Rectangle class. Example 6-2 shows how that same class relationship can be implemented with composition instead of inheritance. In Example 6-2, the Rectangle class is unchanged. But this time, the Square class does not extend Rectangle. Instead, it defines a variable, r, that contains a Rectangle instance. All operations on r are filtered through Square’s public methods. The Square class forwards, or dele- gates, method calls to r. Notice that because the Square class’s setSize( ) method does not override the Rectangle class’s setSize( ) method, its signature need not be compat- ible with the Rectangle class’s setSize( ) method. The Square class’s setSize( ) method is free to define a single parameter, contrasting with the Rectangle class’s setSize( ) method, which defines two parameters. Example 6-2. An example composition relationship // The Rectangle class public class Rectangle { protected var w = 0; protected var h = 0; public function Rectangle (width, height) { setSize(width, height); } public function setSize (newW, newH) { w = newW; h = newH; } public function getArea ( ) { return w * h; } The Theory of Inheritance | 119 Is-A, Has-A, and Uses-A In object-oriented language, an inheritance relationship is known colloquially as an Is-A relationship because an instance of the subclass can be seen literally as being an instance of the superclass (i.e., the subclass instance can be used wherever a super- class instance is expected). In our earlier polymorphic example, a Circle instance Is-A Shape because the Circle class inherits from the Shape class and can, therefore, be used anywhere a Shape is used. A composition relationship is known as a “Has-A relationship because the front-end class maintains an instance of the back-end class. The Has-A relationship should not be confused with the Uses-A relationship, in which a class instantiates an object of another class but does not assign it to an instance variable. In a “Uses-A relationship, the class uses the object and throws it away. For example, a Circle might store its numeric color in a variable, color (Has-A uint object), but then use a Color object temporarily to actually set that color on screen (Uses-A Color object). In Example 6-2, our Square class Has-A Rectangle instance and adds restrictions to it that effectively turn it into a Square. In the case of Square and Rectangle, the Is-A relationship seems natural, but the Has-A relationship can also be used. Which begs the question: which relationship is best? When to use composition over inheritance Example 6-2 raises a serious design question. How do you choose between composi- tion and inheritance? In general, it’s fairly easy to spot a situation in which inherit- ance is inappropriate. An AlertDialog instance in an application “has an” OK button, but an AlertDialog instance, itself, “isn’t an” OK button. However, spotting a situation in which composition is inappropriate is trickier because any time you can } // Here's the new Square class public class Square { private var r; public function Square (side) { r = new Rectangle(side, side); } public function setSize (side) { r.setSize(side, side); } public function getArea ( ) { return r.getArea( ); } } Example 6-2. An example composition relationship (continued) 120 | Chapter 6: Inheritance use inheritance to establish the relationship between two classes, you could use com- position instead. If both techniques work in the same situation, how can you tell which is the best option? If you’re new to object-oriented programming, you may be surprised to hear that composition is often favored over inheritance as an application design strategy. In fact, some of the best-known object-oriented design theoreticians explicitly advocate composition over inheritance (see Design Patterns: Elements of Reusable Object-Ori- ented Software, Gamma et al., 1995, Addison-Wesley). Hence, conventional wisdom tells us to at least consider composition as an option even when inheritance seems obvious. That said, here are some general guidelines to consider when deciding whether to use inheritance or composition: • If you want to take advantage of polymorphism, consider using inheritance. • If a class just needs the services of another class, consider a composition relationship. • If a class you’re designing behaves very much like an existing class, consider an inheritance relationship. For more advice on choosing between composition and inheritance, read Bill Ven- ner’s excellent JavaWorld article, archived at his site: http://www.artima.com/ designtechniques/compoinh.html. Mr. Venner offers compelling evidence that, gener- ally speaking: • Changing code that uses composition has fewer consequences than changing code that uses inheritance. • Code based on inheritance often executes faster than code based on composition. Abstract Not Supported Many object-oriented designs require the use of a so-called abstract class. An abstract class is any class that defines zero or more abstract methods—methods that have a name, parameters, and a return type but no implementation (i.e., no method body). A class that wishes to extend an abstract class must either implement all of the super- class’s abstract methods, or be abstract itself; otherwise, a compile-time error occurs. Subclasses of an abstract class effectively promise to provide some real code to do a job the abstract class only describes in theory. Abstract classes are a common, important part of polymorphic designs. For exam- ple, in our earlier discussion of polymorphism, we studied a Shape class with Circle, Rectangle, and Triangle subclasses. Traditionally, the Shape class’s draw( ) method would be defined as an abstract method, guaranteeing that: • Each Shape subclass provides a means of drawing itself to the screen. • External code can safely call draw( ) on any Shape subclass (because the com- piler will not let a class extend Shape without implementing draw( )). Using Inheritance in the Virtual Zoo Program | 121 Unfortunately, ActionScript does not support abstract classes or abstract methods. Instead of defining an abstract method in ActionScript, you should simply define a method with no code in its body, and document the method as “abstract.” It’s left up to the programmer (not the compiler) to ensure that the subclasses of a would-be abstract class implement the appropriate method(s). In many cases, ActionScript interfaces can be used in place of abstract classes to enforce a particular object-oriented architecture. See Chapter 9. We’ve finished our study of inheritance. Let’s close this chapter by applying our new knowledge to the virtual zoo program. Using Inheritance in the Virtual Zoo Program In our virtual zoo program, we’ll use inheritance in two different ways. First, we’ll use it to define types of food for our pets to eat—replacing our earlier approach of adding raw calories to pets through the eat( ) method. Second, we’ll use inheritance to make our application’s main class, VirtualZoo, displayable on screen. Creating Types of Pet Food Until now, our implementation of eating in the virtual zoo program has been overly simplistic. To make a pet eat, we simply invoke eat( ) on the desired VirtualPet object and specify an arbitrary number of calories for the pet to eat. To improve the realism of our simulation, let’s add types of food to the zoo program. To keep things simple, we’ll allow our pet to eat only two types of food: sushi and apples. We’ll represent sushi with a new class, Sushi, and apples with a new class, Apple. Because the Sushi and Apple classes both conceptually represent food, they have nearly identical functionality. Hence, in our application, we’ll implement the functionality needed by both Sushi and Apple in a single superclass, Food. The Sushi and Apple classes will extend Food and, through inheritance, adopt its features. The Food class defines four simple methods for retrieving and modifying the name and calorie value of a given piece of food. Here’s the code: package zoo { public class Food { private var calories; private var name; public function Food (initialCalories) { setCalories(initialCalories); } public function getCalories ( ) { return calories; } 122 | Chapter 6: Inheritance public function setCalories (newCalories) { calories = newCalories; } public function getName ( ) { return name; } public function setName (newName) { name = newName; } } } The Apple class sets the default number of calories for a given Apple object, and defines the food name for all Apple objects. Here’s the code: package zoo { public class Apple extends Food { // Set the default number of calories for an Apple object to 100 private static var DEFAULT_CALORIES = 100; public function Apple (initialCalories = 0) { // If no calorie value or a negative calorie value was specified // for this particular object... if (initialCalories <= 0) { // ...use the default value initialCalories = Apple.DEFAULT_CALORIES; } super(initialCalories); // Set the food name for all Apple objects setName("Apple"); } } } The Sushi class sets the default number of calories for a given Sushi object, and defines the food name for all Sushi objects. Here’s the code: package zoo { public class Sushi extends Food { private static var DEFAULT_CALORIES = 500; public function Sushi (initialCalories = 0) { if (initialCalories <= 0) { initialCalories = Sushi.DEFAULT_CALORIES; } super(initialCalories); setName("Sushi"); } } } Using Inheritance in the Virtual Zoo Program | 123 To enable VirtualPet objects to eat apples and sushi, we need to update the VirtualPet class’s eat( ) method. Here’s what the eat( ) method used to look like: public function eat (numberOfCalories) { if (currentCalories == 0) { trace(getName( ) + " is dead. You can't feed it."); return; } var newCurrentCalories = currentCalories + numberOfCalories; if (newCurrentCalories > VirtualPet.maxCalories) { currentCalories = VirtualPet.maxCalories; } else { currentCalories = newCurrentCalories; } trace(getName( ) + " ate some food. It now has " + currentCalories + " calories remaining."); } In the new version of the eat( ) method, we’ll change the numberOfCalories parame- ter to foodItem, introducing the logical convention that eat( )’s argument must be an instance of any class that inherits from Food. (Note that Chapter 8 teaches how to enforce that convention with a datatype declaration.) Within the eat( ) method, we’ll calculate newCurrentCalories by adding the calorie value of the food item (i.e., foodItem.getCalories( )) to the pet’s existing calories (i.e., currentCalories). Finally, when reporting that the pet ate some food, we’ll use the Food class’s getName( ) method to list the name of the food that was eaten. Here’s the updated eat( ) method: public function eat (foodItem) { if (currentCalories == 0) { trace(getName( ) + " is dead. You can't feed it."); return; } var newCurrentCalories = currentCalories + foodItem.getCalories( ); if (newCurrentCalories > VirtualPet.maxCalories) { currentCalories = VirtualPet.maxCalories; } else { currentCalories = newCurrentCalories; } trace(getName() + " ate some " + foodItem.getName( ) + "." + " It now has " + currentCalories + " calories remaining."); } Now let’s try feeding some sushi and an apple to the pet in the VirtualZoo construc- tor. Here’s the code: package zoo { public class VirtualZoo { private var pet; public function VirtualZoo ( ) { pet = new VirtualPet("Stan"); 124 | Chapter 6: Inheritance pet.eat(new Apple( )); // Feed Stan an apple pet.eat(new Sushi( )); // Feed Stan some sushi } } } The Sushi and Apple classes are currently very simple, but they provide the founda- tion for more sophisticated behavior. For example, now that our zoo program includes types of food, we could quite easily make pets that like only apples or eat sushi after 6 p.m. We can also easily customize the behavior of each type of food. As an example, let’s randomly give 50% of all apples a worm, and make pets reject apples with worms. To track whether a given Apple object has a worm, we’ll add a new instance variable to the Apple class, wormInApple: private var wormInApple; Next, within the Apple constructor, we’ll use Math.random( ) to pick a random num- ber between 0 and 0.9999.... If the number is equal to or greater than 0.5, we’ll assign wormInApple the value true, indicating that the Apple object has a worm; oth- erwise we’ll assign wormInApple the value false, indicating that the Apple object does not have a worm. Here’s the code: wormInApple = Math.random( ) >= .5; To give other classes a way to check whether an Apple object has a worm, we’ll define a new public method, hasWorm( ), which simply returns the value of wormInApple. Here’s the code: public function hasWorm ( ) { return wormInApple; } Finally, we’ll update the VirtualPet class’s eat( ) method to make pets reject apples with worms. Here’s the code, excerpted from the eat( ) method: if (foodItem is Apple) { if (foodItem.hasWorm( )) { trace("The " + foodItem.getName() + " had a worm. " + getName( ) + " didn't eat it."); return; } } In the preceding code, notice the use of the is operator, which checks whether an object is an instance of a specified class or any class that inherits from that class. The expression foodItem is Apple yields the value true if foodItem refers to an instance of Apple (or any class that inherits from Apple); otherwise, it yields false. If the foodItem is an Apple object, and its hasWorm( ) method returns true, then the eat( ) method is terminated without increasing the value of currentCalories. Beyond types of food, there’s one other use of inheritance in the virtual zoo pro- gram. Let’s take a look. Using Inheritance in the Virtual Zoo Program | 125 Preparing VirtualZoo for Onscreen Display Because ActionScript is used to create graphical content and user interfaces, the main class of every ActionScript program must extend either the flash.display.Sprite class or the flash.display.MovieClip class. Both Sprite and MovieClip represent containers for onscreen graphical content. The MovieClip class is sometimes used when the pro- gram’s main class is associated with a .fla file (Flash authoring tool document), as described in Chapter 29. Otherwise, the Sprite class is used. When a Flash runtime opens a new .swf file, it creates an instance of that .swf file’s main class and adds that instance to a hierarchical list of objects that are currently displayed on screen, known as the display list. Once the instance is on the display list, it can then use the inherited methods of the DisplayObject class (from which Sprite or MovieClip both descend) to add other graphical content to the screen. Our virtual zoo program will eventually add graphical content to the screen. But before it does, we have much to learn about the display list and graphics program- ming. Part II of this book, explores those topics in great detail. For now, however, in order to run our program in its current state, we must meet the requirement that the main class of every ActionScript program must extend either the Sprite class or the MovieClip class. Our program contains no Flash authoring tool content, so its main class, VirtualZoo, extends the Sprite class. Here’s the code: package zoo { import flash.display.Sprite; public class VirtualZoo extends Sprite { private var pet; public function VirtualZoo ( ) { pet = new VirtualPet("Stan"); pet.eat(new Apple( )); pet.eat(new Sushi( )); } } } We’ve made quite a few changes to our virtual zoo program in this chapter. Let’s review the code in its entirety. 126 | Chapter 6: Inheritance Virtual Zoo Program Code Example 6-3 shows the code for the VirtualZoo class, the program’s main class. Example 6-4 shows the code for the VirtualPet class, whose instances represent pets in the zoo. Example 6-3. The VirtualZoo class package zoo { import flash.display.Sprite; public class VirtualZoo extends Sprite { private var pet; public function VirtualZoo ( ) { pet = new VirtualPet("Stan"); pet.eat(new Apple( )); pet.eat(new Sushi( )); } } } Example 6-4. The VirtualPet class package zoo { import flash.utils.setInterval; import flash.utils.clearInterval; internal class VirtualPet { private static var maxNameLength = 20; private static var maxCalories = 2000; private static var caloriesPerSecond = 100; private var petName; private var currentCalories = VirtualPet.maxCalories/2; private var digestIntervalID; public function VirtualPet (name) { setName(name); digestIntervalID = setInterval(digest, 1000); } public function eat (foodItem) { if (currentCalories == 0) { trace(getName( ) + " is dead. You can't feed it."); return; } if (foodItem is Apple) { if (foodItem.hasWorm( )) { trace("The " + foodItem.getName() + " had a worm. " + getName( ) + " didn't eat it."); Virtual Zoo Program Code | 127 return; } } var newCurrentCalories = currentCalories + foodItem.getCalories( ); if (newCurrentCalories > VirtualPet.maxCalories) { currentCalories = VirtualPet.maxCalories; } else { currentCalories = newCurrentCalories; } trace(getName() + " ate some " + foodItem.getName( ) + "." + " It now has " + currentCalories + " calories remaining."); } public function getHunger ( ) { return currentCalories / VirtualPet.maxCalories; } public function setName (newName) { // If the proposed new name has more than maxNameLength characters... if (newName.length > VirtualPet.maxNameLength) { // ...truncate it newName = newName.substr(0, VirtualPet.maxNameLength); } else if (newName == "") { // ...otherwise, if the proposed new name is an empty string, // then terminate this method without changing petName return; } // Assign the new, validated name to petName petName = newName; } public function getName ( ) { return petName; } private function digest ( ) { // If digesting more calories would leave the pet's currentCalories at // 0 or less... if (currentCalories - VirtualPet.caloriesPerSecond <= 0) { // ...stop the interval from calling digest( ) clearInterval(digestIntervalID); // Then give the pet an empty stomach currentCalories = 0; // And report the pet's death trace(getName( ) + " has died."); } else { // ...otherwise, digest the stipulated number of calories currentCalories -= VirtualPet.caloriesPerSecond; // And report the pet's new status Example 6-4. The VirtualPet class (continued) 128 | Chapter 6: Inheritance Example 6-5 shows the code for the Food class, the superclass of the various types of food that pets eat. Example 6-6 shows the code for the Apple class, which represents a specific type of food that pets eat. trace(getName( ) + " digested some food. It now has " + currentCalories + " calories remaining."); } } } } Example 6-5. The Food class package zoo { public class Food { private var calories; private var name; public function Food (initialCalories) { setCalories(initialCalories); } public function getCalories ( ) { return calories; } public function setCalories (newCalories) { calories = newCalories; } public function getName ( ) { return name; } public function setName (newName) { name = newName; } } } Example 6-6. The Apple class package zoo { public class Apple extends Food { private static var DEFAULT_CALORIES = 100; private var wormInApple; public function Apple (initialCalories = 0) { if (initialCalories <= 0) { initialCalories = Apple.DEFAULT_CALORIES; Example 6-4. The VirtualPet class (continued) It’s Runtime! | 129 Finally, Example 6-7 shows the code for the Sushi class, which represents a specific type of food that pets eat. It’s Runtime! With the changes we made to the virtual zoo program in this chapter, our applica- tion is now ready to compile and run. I hope it is with more than a little excitement that you precede to the next chapter, where we’ll learn how to run our program after compiling it with the Flash authoring tool, Flex Builder, or mxmlc. } super(initialCalories); wormInApple = Math.random( ) >= .5; setName("Apple"); } public function hasWorm ( ) { return wormInApple; } } } Example 6-7. The Sushi class package zoo { public class Sushi extends Food { private static var DEFAULT_CALORIES = 500; public function Sushi (initialCalories = 0) { if (initialCalories <= 0) { initialCalories = Sushi.DEFAULT_CALORIES; } super(initialCalories); setName("Sushi"); } } } Example 6-6. The Apple class (continued) 130 Chapter 7CHAPTER 7 Compiling and Running a Program 8 After all our hard work on the virtual zoo program, we are now ready to compile and run our code. In this chapter, we’ll learn how to compile a program with the Flash authoring tool, FlexBuilder 2, and mxmlc. In each case, we’ll assume that the pro- gram being compiled resides in the folder /virtualzoo/, and that the source code for the program (i.e., the .as files) resides in the folder /virtualzoo/src/. Now let’s do some compiling! Compiling with the Flash Authoring Tool To compile the virtual zoo program using the Flash authoring tool, we must first associate the program’s main class with a .fla file, as described in the following steps: 1. In the Flash authoring tool, select File ➝ New. 2. On the New Document dialog, select Flash File (ActionScript 3.0), then click OK. 3. Select File ➝ Save As. 4. On the Save As dialog, for Save in, browse to the /virtualzoo/src folder. 5. On the Save As dialog, for File name, enter VirtualZoo.fla, then click OK. 6. On the Properties panel, under Document class, enter zoo.VirtualZoo. Once the program’s main class has been associated with a .fla file (as described in the preceding steps), we then select Control ➝ Test Movie to compile the program and run the resulting .swf file in a test version of Flash Player directly within the Flash authoring tool. When the program runs in “Test Movie” mode, trace( ) messages appear in the Flash authoring tool’s Output panel. (You should see Stan getting hungry!) When a program is compiled using Test Movie, the Flash authoring tool generates a .swf file with a name matching the corresponding .fla file. For example, when we compile the virtual zoo program using Test Movie, a new file, VirtualZoo.swf, Compiling with Flex Builder 2 | 131 appears in the /src/ folder. For information on changing the folder in which the generated .swf file is placed, see the Flash authoring tool’s documentation for the File ➝ Publish Settings command. To distribute VirtualZoo.swf over the Web, we would typically embed it in an HTML page. For details, see the Flash authoring tool’s documentation for the File ➝ Publish command. To distribute VirtualZoo.swf as a desktop application, we would bundle it into an installable .air file. For details see the product documentation for Adobe AIR. Compiling with Flex Builder 2 Before we can compile the virtual zoo program using FlexBuilder 2, we must first make some changes to our code in order to meet the requirements of FlexBuilder 2’s compiler. FlexBuilder 2’s compiler stipulates that a program’s main class must reside in the unnamed package. Currently, our VirtualZoo class resides in the pack- age zoo, not the unnamed package. Moving the Main Class to the Unnamed Package To move VirtualZoo from zoo to the unnamed package, we must follow these steps: 1. Move the file VirtualZoo.as from /virtualzoo/src/zoo to /virtualzoo/src/. 2. In the VirtualZoo.as file, add the following code immediately before the VirtualZoo class definition statement (this code imports the classes from the zoo package): import zoo.*; 3. In the VirtualZoo.as file, remove the package name “zoo” from the package dec- laration statement. That is, change this code: package zoo { to this: package { 4. In the VirtualPet.as file, change the access-control modifier for the VirtualPet class from internal to public, as follows (this gives the VirtualZoo class access to the VirtualPet class): public class VirtualPet { Once the preceding changes have been made, we can then compile the program. Compiling the Program To compile the virtual zoo program, we first create an ActionScript Project, as described in the following steps: 1. Select File ➝ New ➝ ActionScript Project. 2. On the New ActionScript Project dialog, for Project name, enter virtualzoo. 132 | Chapter 7: Compiling and Running a Program 3. Under Project Contents, uncheck “Use default location”. 4. Under Project Contents, for Folder, enter (or browse to) the location of the virtualzoo folder on your hard drive. 5. Click Next. 6. For Main source folder, enter src. 7. For Main application file, enter VirtualZoo.as. 8. Click Finish. Running the Program Once the ActionScript Project has been created, we can then follow these steps to run the virtual zoo program in debugging mode so that trace( ) messages appear in the Console panel: 1. In the navigator panel, select any class in the virtual zoo project. 2. Select Run ➝ Debug VirtualZoo. By default, the program will launch in the sys- tem default web browser. In the process of compiling the program, FlexBuilder 2 generates the following assets, which it places in an automatically created folder, /virtualzoo/bin/: •A.swf file named VirtualZoo.swf •A.swf file named VirtualZoo-debug.swf, used for debugging • An HTML file named VirtualZoo.html, which embeds VirtualZoo.swf for web distribution • An HTML file named VirtualZoo-debug .html, which embeds VirtualZoo-debug. swf for testing in a web browser • A series of supporting files for web browser-based detection of Flash Player and automatic Flash Player installation To distribute VirtualZoo.swf over the Web, simply place all the files from the /bin/ folder—except for VirtualZoo-debug.html and VirtualZoo-debug.swf—in a folder on a public web server. To distribute the program as a desktop application, see the product documentation for Adobe AIR. Compiling the virtual zoo program as described in this section will cause a series of compiler warnings such as “var ‘pet’ has no type dec- laration.” For now, you can simply ignore these warnings. In the next chapter, we’ll learn why they occur. Compiling with mxmlc | 133 Compiling with mxmlc Like FlexBuilder 2’s compiler, mxmlc stipulates that a program’s main class must reside in the unnamed package. Therefore, before compiling the virtual zoo program with mxmlc, we must first move VirtualZoo from zoo to the unnamed package by following the steps listed in the earlier section “Moving the Main Class to the Unnamed Package.” Next, we must locate the compiler itself, which is named mxmlc.exe. The location of the compiler varies by version and operating system. Typically, it resides in a folder called Flex SDK [version]\bin, but you should confirm the location for your com- puter according to the documentation provided with FlexSDK. For the purposes of this example, we’ll assume that we’re compiling on Windows XP, and that the com- piler resides in the following location: C:\Flex SDK 2\bin\mxmlc.exe We’ll also assume that our /virtualzoo/ program folder resides in the following location: C:\data\virtualzoo\ To compile the virtual zoo program using mxmlc, we follow these steps: 1. From the Windows start menu, open a command prompt by choosing Start ➝ All Programs ➝ Accessories ➝ Command Prompt. 2. At the command prompt, change to the C:\Flex SDK 2\bin\ directory by entering the following command: cd C:\Flex SDK 2\bin 3. At the command prompt, enter the following command, then press Enter: mxmlc C:\data\virtualzoo\src\VirtualZoo.as In response to the preceding steps, mxmlc compiles the program, and generates a .swf file named VirtualZoo.swf, which it places in the virtualzoo\src folder. Note that mxmlc has a wide variety of compilation options; for details, see the documentation provided with Flex SDK. To run the VirtualZoo.swf file generated by mxmlc, simply open it in the standalone version of Flash Player or in a web browser with Flash Player installed. To view the trace( ) messages generated by the program, use the debugger version of Flash Player (included with FlexSDK) and configure it to output trace( ) messages to a logfile. For details, see http://livedocs.adobe.com/flex/201/html/logging_125_07.html. Compiling the virtual zoo program as described in this section will cause a series of compiler warnings such as “var ‘pet’ has no type dec- laration.” For now, you can simply ignore these warnings. In the next chapter, we’ll learn why they occur. 134 | Chapter 7: Compiling and Running a Program Compiler Restrictions When compiling ActionScript programs with the Flash authoring tool, FlexBuilder, or mxmlc, bear in mind the following compiler restrictions: • The program’s main class must be public. • In FlexBuilder 2 and mxmlc, the program’s main class must reside in the unnamed package. • The program’s main class must extend either Sprite or MovieClip, as discussed in Chapter 6. • Every ActionScript source file (.as file) in the program must have exactly one externally visible definition. An “externally visible definition” is a class, variable, function, interface, or namespace that is defined as either internal or public within a package statement. • An ActionScript source file’s name must match the name of its sole externally visible definition. For example, the following source file would be considered illegal because it con- tains two externally visible classes: package { public class A { } public class B { } } Likewise, the following source file would be considered illegal because it does not contain any externally visible definition. class C { } The Compilation Process and the Classpath When a .swf file is exported, the ActionScript compiler makes a list of all the classes that the .swf requires. Specifically, the list of required classes includes: • All classes referenced directly or indirectly by the program’s main class • In the case of the Flash authoring tool, all the classes referenced directly or indi- rectly by the .swf’s source .fla file (i.e., in frame scripts) The compiler searches the filesystem for source .as files that correspond to all refer- enced classes, and compiles each source file into the .swf, in bytecode format. The set of folders in which the compiler searches for .as files is known as the classpath. Strict-Mode Versus Standard-Mode Compilation | 135 Class files that exist on the filesystem but are not required by the .swf are not compiled into the .swf, classes that are required but not found cause a compile-time error. Each ActionScript authoring tool includes some folders in the classpath automatically and also allows you to specify directories that should be included in the classpath. For example, the Flash authoring tool automatically includes the folder containing the .swf ’s source .fla file in a classpath. Likewise, FlexBuilder 2 and mxmlc both automatically include the folder containing the program’s main class in the classpath. For instructions on including other folders in the classpath, see the appropriate product’s documentation. The classpath is sometimes also referred to as the build path or the source path. Strict-Mode Versus Standard-Mode Compilation ActionScript offers two different modes for compiling a program: strict mode and standard mode. In strict mode, the compiler reports more errors than in standard mode. The extra strict-mode errors are intended to help programmers locate poten- tial sources of problems in a program before the program actually runs. Strict mode is, therefore, enabled by default in all of Adobe’s compilers. Programmers who wish to use ActionScript’s dynamic features (as described in Chapter 15,), or who simply prefer to solve problems (i.e., debug) at runtime rather than at compile time can choose to compile using standard mode. The following questionable acts of programming will cause a compiler error in strict mode, but not in standard mode: • Supplying the wrong number or types of parameters to a function (see Chapter 8) • Defining two variables or methods with the same name • Accessing methods and variables that are not defined at compile time (but might be defined at runtime using the techniques described in Chapter 15) • Assigning a value to a nonexistent instance variable of an object whose class is not dynamic • Assigning a value to a constant variable anywhere other than the variable’s ini- tializer or, for instance variables, the constructor method of the class containing the variable’s definition • Attempting to delete (via the delete operator) an instance method, instance vari- able, static method, or static variable • Comparing two incompatibly typed expressions (see the section “Compatible Types” in Chapter 8) 136 | Chapter 7: Compiling and Running a Program • Assigning a value to a type-annotated variable where the value is not a member of the declared type (for exceptions to this rule, see the section “Strict Mode’s Three Special Cases” in Chapter 8) • Referring to nonexistent packages Enabling Standard-Mode Compilation in Flex Builder 2 To enable standard-mode compilation for a project in FlexBuilder 2, follow these steps: 1. In the Navigator, select the project folder. 2. Select Project ➝ Properties. 3. Under ActionScript Compiler, uncheck “Enable strict type checking.” Enabling Standard-Mode Compilation in the Flash Authoring Tool To enable standard-mode compilation for a document in the Flash authoring tool, follow these steps: 1. Select File ➝ Publish Settings. 2. On the Publish Settings dialog, on the Flash tab, click the Settings button. 3. On the ActionScript 3.0 Settings dialog, under Errors, uncheck Strict Mode. To enable standard-mode compilation when compiling with mxmlc, set the com- piler option strict to false. The Fun’s Not Over We’ve now compiled and run our virtual zoo program, but our program isn’t done yet. To make the zoo fully interactive, complete with graphics and buttons for feed- ing pets, we need to continue our study of ActionScript essentials. In the next chap- ter, we’ll learn how ActionScript’s type-checking system helps detect common errors in a program. 137 Chapter 8 CHAPTER 8 Datatypes and Type Checking9 So far, we’ve developed our virtual zoo program without making a single coding error. Error-free development happens in training courses and books—and nowhere else. In real-world development, programmers make errors all the time. For exam- ple, when invoking eat( ) on a VirtualPet object, a programmer might make a typo- graphical error, such as the following (notice the extra “t”): pet.eatt(new Sushi( )) Or, a programmer might make a mistaken assumption about the capabilities of an object. For example, a programmer might mistakenly attempt to invoke a method named jump( ) on a VirtualPet object, even though VirtualPet defines no such method: pet.jump( ) In both the preceding cases, when the program runs in the debugger version of a Flash runtime, ActionScript will generate a reference error, indicating that the pro- gram attempted to reference a variable or method that doesn’t exist. Errors that occur at runtime are known as exceptions. We’ll study exceptions and the techniques for handling them in Chapter 13. When an error occurs in a program you’re writing, you should be happy. Errors indi- cate the precise location and cause of something in your program that would likely cause a malfunction without your attention. For example, in response to the earlier “eatt( )” typo, the debugger version of a Flash runtime would display an alert dialog containing the following message: ReferenceError: Error #1069: Property eatt not found on zoo.VirtualPet and there is no default value. at VirtualZoo$iinit( )[C:\data\virtualzoo\src\VirtualZoo.as:8] The error message tells us not only the name of the file in which the error occurred, but also the specific line of code containing the error. Now that’s service. (Notice 138 | Chapter 8: Datatypes and Type Checking that the error message uses the term property to mean “variable or method,” as dis- cussed in Chapter 1 under “Members and Properties.”) As useful as runtime errors are, they have a potential drawback. They occur only when the erroneous line of code actually runs. Therefore, in a very large program, a runtime error might take a long time to surface. For example, in an adventure game that takes 10 hours to complete, an error in the final stage would take 10 hours of game-play to surface! Luckily, rather than waiting for reference errors to occur at runtime, we can tell the compiler to report them at compiletime, before a program ever runs. To do so, we use type annotations in combination with strict mode compilation. Datatypes and Type Annotations In ActionScript, the term datatype means, simply, “a set of values.” ActionScript defines three fundamental datatypes: Null, void, and Object. The Null and void datatypes each include a single value only—null and undefined, respectively (null and undefined are discussed in the later section “null and undefined”). The Object datatype includes all instances of all classes. In addition to the three fundamental datatypes (Null, void, and Object), each and every built-in or custom class constitutes a unique datatype whose set of values includes its direct instances and instances of its descendant classes. For example, the Food class from our virtual zoo program constitutes a datatype whose set of values includes all instances of Food and all instances of Apple and Sushi (because Apple and Sushi both inherit from Food). Thus, an Apple instance and a Sushi instance are both said to belong to the Food datatype. But the Apple class and the Sushi class each also constitute their own datatype. For example, the set of values in the Apple datatype includes all Apple instances and all instances of any class that inherits from Apple. Likewise, the set of values in the Sushi datatype includes all Sushi instances and all instances of any class that inherits from Sushi. Thus, in addition to belonging to the Food datatype, an Apple instance also belongs to the Apple datatype. But an Apple instance does not belong to the Sushi datatype because Sushi does not inherit from Apple. In the same way, a Sushi instance belongs to the Food and Sushi datatypes, but does not belong to the Apple datatype because Sushi does not inherit from Apple. Finally, while an Apple instance and a Sushi instance both belong to the Food datatype, a Food instance does not belong to either the Apple or Sushi datatypes because the Food class does not inherit from the Apple or Sushi classes. Datatypes and Type Annotations | 139 Notice the important distinction between a given class and the datatype that it represents. The set of values that belong to the class includes the class’s instances only. But the set of values that belong to the class’s datatype includes the class’s instances and instances of its descendant classes. Just as each class constitutes a datatype, each interface also constitutes a datatype. The set of values in an interface’s datatype includes every instance of every class that implements the interface, and every instance of every class that inherits from a class that implements the interface. We haven’t studied interfaces yet, so we’ll defer our examination of interfaces as datatypes until Chapter 9. Given two datatypes A and B, where the class (or interface) represented by B inherits from the class (or interface) represented by A, A is referred to as a supertype of B. Conversely, B is referred to as a subtype of A. For example, Food is considered a supertype of Apple, while Apple is considered a subtype of Food. Compatible Types Because a given class can, through inheritance, use all the nonprivate instance mem- bers of its superclass (or superinterface), a given subtype is considered compatible with any of its supertypes. For example, the Apple datatype is considered compatible with the Food datatype because it is a subtype of Food. The opposite, however, is not true. A class cannot use any of the instance members defined by its descendant classes. Hence, a given supertype is considered incompati- ble with any of its subtypes. For example, the Food datatype is considered incompati- ble with the Apple datatype because Food is not a subtype of Apple. Conceptually, the subtype is considered compatible with the supertype because a program can treat an instance of the subtype as though it were an instance of the supertype. For example, a program can treat any Apple instance as though it were a Food instance—perhaps by invoking the Food class’s getCalories( ) method on it. // Create a new Apple instance var apple = new Apple( ); // Legally invoke getCalories( ) on the Apple instance apple.getCalories( ); By comparison, the supertype is considered incompatible with the subtype because a program cannot treat an instance of the supertype as though it were an instance of the subtype. For example, a program cannot invoke the Apple class’s hasWorm( ) method on a Food instance: // Create a new Food instance var food = new Food(200); 140 | Chapter 8: Datatypes and Type Checking // The following line causes a reference error because the Food class // has no access to the hasWorm( ) method food.hasWorm( ); // Error! Detecting Type Mismatch Errors with Type Annotations A type annotation (or, synonymously, a type declaration) is a suffixthat constrains the datatype of a variable, parameter, or function return value. The general syntax for a type annotation is a colon (:) followed by a datatype, as in: :type For example, a variable definition with a type annotation has the following general- ized form: var identifier:type = value; In the preceding code, the type must be the name of a class or interface (represent- ing the datatype), or the special symbol * (indicating “untyped”). A function or method definition with a parameter type annotation and a return type annotation has the following generalized form: function identifier (param:paramType):returnType { } In the preceding code, the parameter type annotation is the specified paramType and the colon (:) that precedes it; the return type annotation is the specified returnType and the colon (:) that precedes it. The paramType must be one of the following: • The name of a class or interface (representing the datatype) • The special symbol, * (indicating “untyped”) The returnType must be one of the following: • The name of a class or interface (representing the datatype) • The special symbol, * (indicating “untyped”) • The special “no-return” type annotation, void (indicating that the function does not return a value) ActionScript 2.0 programmers should note that Void is no longer capi- talized in ActionScript 3.0. A type annotation for a variable, parameter, or function result constrains the value of that variable, parameter, or result to the specified type. The means by which the value is constrained depends on the compilation mode used to compile the code (recall that strict mode is the default compilation mode for Adobe compilers). Datatypes and Type Annotations | 141 In both standard mode and strict mode, if the value belongs to the specified datatype, then the assignment or return attempt succeeds. If the value does not belong to the specified datatype, then in strict mode, the com- piler generates an error (known as a type mismatch error), and refuses to compile the code. In standard mode, the code compiles, and at runtime ActionScript attempts to convert the value to the specified datatype. If the specified datatype is one of the built-in classes String, Boolean, Number, int,oruint (known as the primitive types) then the conversion proceeds according to the rules described in the section “Con- version to Primitive Types,” later in this chapter. Otherwise, the conversion fails, and ActionScript generates a runtime type mismatch error. In formal terms, automatic runtime-conversion is known as coercion. For example, the following code defines a variable, meal, of type Food, and assigns that variable an instance of the Apple class: var meal:Food = new Apple( ); In both strict mode and standard mode, the preceding code compiles successfully because Apple extends Food, so instances of Apple belong to the Food datatype. By contrast, the following code assigns meal an instance of the VirtualPet class: var meal:Food = new VirtualPet("Lucky"); In strict mode, the preceding code causes a type mismatch error because VirtualPet instances do not belong to the Food datatype. Therefore, the code fails to compile. In standard mode, the preceding code compiles happily. However, because the value (the VirtualPet instance) does not belong to the variable’s datatype (Food), Action- Script attempts to coerce (i.e., convert) the value to the variable’s datatype at run- time. In this case, the variable’s datatype is not one of the primitive types, so the conversion fails, and ActionScript generates a runtime type mismatch error. Let’s look at another example. The following code defines a variable, petHunger,of type int, and assigns that variable an instance of the VirtualPet class: var pet:VirtualPet = new VirtualPet("Lucky"); var petHunger:int = pet; In strict mode, the preceding code causes a type mismatch error because VirtualPet instances do not belong to the int datatype. Hence, the code fails to compile. In standard mode, the preceding code compiles happily. However, because the value (the VirtualPet instance) does not belong to the variable’s datatype (int), Action- Script attempts to convert the value to the variable’s datatype at runtime. In this case, the variable’s datatype is one of the primitive types, so the conversion proceeds according to the rules described in the section “Conversion to Primitive Types,” later in this chapter. Hence, after the code runs, petHunger has the value 0. 142 | Chapter 8: Datatypes and Type Checking Of course, assigning the value 0 to petHunger was likely not the intention of the pre- ceding code. More likely, the programmer simply forgot to invoke getHunger( ) on the VirtualPet instance, as in: var pet:VirtualPet = new VirtualPet("Lucky"); var petHunger:int = pet.getHunger( ); Strict mode faithfully warned us of the problem, but standard mode did not. Instead, standard mode assumed that because petHunger’s datatype was int, we wanted to convert the VirtualPet object to the int type. For our purposes, that assumption was incorrect, and resulted in an unexpected value in our program. Some programmers consider standard mode’s lenience convenient, particularly in simple programs. In more complexprograms, however, standard mode’s flexibility often leaves would-be errors unreported, leading to difficult-to-diagnose bugs. The remainder of this book assumes that all code is compiled in strict mode and supplies type annotations for all variables, parameters, and return values. Untyped Variables, Parameters, Return Values, and Expressions A variable or parameter whose definition includes a type annotation is said to be a typed variable or typed parameter. Likewise, a function definition that includes a return-type annotation is said to have a typed return value. Furthermore, an expres- sion that refers to a typed variable or a typed parameter, or calls a function with typed return value is known as a typed expression. Conversely, a variable or parameter whose definition does not include a type annota- tion is said to be an untyped variable or untyped parameter. Likewise, a function defi- nition that does not include a return-type annotation is said to have an untyped return value. An expression that refers to an untyped variable or an untyped parameter, or calls a function with an untyped return value is known as a untyped expression. Untyped variables, parameters, and return values are not constrained to a specific datatype (as typed variables, parameters, and return values are). For example, an untyped variable can be assigned a Boolean value on one line, and a VirtualPet object on another without error: var stuff = true; stuff = new VirtualPet("Edwin"); // No error ActionScript does not generate type-mismatch errors for untyped vari- ables, parameters, and return values. Strict Mode’s Three Special Cases | 143 Programmers wishing to explicitly indicate that a variable, parameter, or return value is intentionally untyped can use the special type annotation, :*. For example, the fol- lowing code defines an explicitly untyped variable, totalCost: var totalCost:* = 9.99; The following code defines the same variable, but this time it is implicitly untyped: var totalCost = 9.99; Implicitly untyped variables, parameters, and return values, are typically used when an entire program does not use type annotations, preferring to handle any type errors at runtime. Explicitly untyped variables, parameters, and return values are typically used when a strict-mode program wishes to specify individual cases where multiple data types are allowed. The :* type annotation prevents an untyped variable from generating a “missing type annotation” warning. For details, see the upcoming sec- tion “Warnings for Missing Type Annotations.” Strict Mode’s Three Special Cases There are three situations in which the compiler ignores type mismatch errors in strict mode, deferring possible type errors until runtime: • When an untyped expression is assigned to a typed variable or parameter, or returned from a function with a declared return type • When any expression is assigned to a typed variable or parameter whose declared type is Boolean, or returned from a function whose declared return type is Boolean • When any numeric value is used where an instance of a different numeric type is expected Let’s look at each of the preceding cases with an example. First, we’ll create an untyped variable, pet, and assign the value of that variable to a typed variable, d: var pet:* = new VirtualPet("Francis"); pet = new Date( ); var d:Date = pet; Because pet can contain any type of value, on line 3, the compiler cannot determine whether pet’s value belongs to the datatype Date. To determine whether the value in pet belongs to the datatype Date, the code must be executed, not just compiled. Once the code is actually executing, ActionScript can then determine the result of the assignment attempt. In the case of the preceding code, the value in pet (assigned on line 2) does indeed belong to the datatype Date (even though pet’s value on line 1 was originally incompatible with Date). Hence, the assignment proceeds without causing an error. 144 | Chapter 8: Datatypes and Type Checking Next, consider the following code which defines a variable, b, of type Boolean, and assigns b an integer value, 5: var b:Boolean = 5; Even though the value 5 does not belong to the Boolean datatype, the compiler does not generate a type mismatch error. Instead, it assumes that the programmer wishes to convert the value 5 to the Boolean datatype (according to the rules described in the later section “Conversion to Primitive Types”) and issues a warning to that effect. This lenience can cut down on the amount of code in a program. For example, sup- pose the VirtualPet class’s getHunger( ) method’s return type were declared as Number. A program could then create a variable indicating whether a pet is alive or dead using the following code: var isAlive:Boolean = somePet.getHunger( ); According to the rules described in the section “Conversion to Primitive Types,” the number 0 converts to the value false, while all other numbers convert to the value true. Hence, if getHunger( ) returns anything other than 0, isAlive is set to true; oth- erwise, isAlive is set to false (the pet is dead when it has no calories left). For comparison, here’s the alternative, slightly longer code that would be necessary if the compiler enforced type checking for variables of type Boolean (rather than allow- ing a runtime conversion): var isAlive:Boolean = somePet.getHunger( ) > 0; Finally, consider the following code that defines a variable, xCoordinate, of type int, and assigns xCoordinate a Number value, 4.6459: var xCoordinate:int = 4.6459; Even though the value 4.6459 does not belong to the int datatype, the compiler does not generate a type mismatch error. Instead, the compiler assumes that you wish to convert the value 4.6459 to the int datatype (according to the rules described in the section “Conversion to Primitive Types”). This lenience allows easy interoperation between ActionScript’s numeric data types with minimal fuss. Warnings for Missing Type Annotations As we’ve seen over the past several sections, ActionScript’s strict compilation mode provides a valuable way to detect program errors as early as possible. Not surpris- ingly, in an effort to write problem-free code, many developers rely heavily on strict mode’s compile-time type checking. However, as we learned in the earlier section “Untyped Variables, Parameters, Return Values, and Expressions,” strict-mode’s type-mismatch errors are reported for typed variables, parameters, and return values only. Any time a type annotation is accidentally omitted, the programmer loses the benefit of strict mode’s compile-time type checking. Detecting Reference Errors at Compile Time | 145 Luckily, Adobe’s ActionScript compilers offer a warning mode in which missing type annotations are reported at compiletime. Developers can use those warnings to help locate accidentally omitted type annotations. In FlexBuilder 2 and mxmlc, warnings for missing type annotations are enabled by default. In the Flash authoring tool, type annotation warnings must be enabled manually, using the following steps: 1. Using a text editor, in the Flash CS3 installation folder, under /en/Configuration/ ActionScript 3.0/, open EnabledWarnings.xml. 2. Locate the following line: Missing type declaration. 3. Change enabled="false" to enabled="true". 4. Save EnabledWarnings.xml. Note that missing type-annotation warnings are issued for implicitly untyped vari- ables, parameters, and return values only. Missing type-annotation warnings are not issued for explicitly untyped variables, parameters, and return values (i.e., those that use the special type annotation, :*). Detecting Reference Errors at Compile Time At the beginning of this chapter, we learned that an attempt to access a nonexistent variable or method results in a reference error. When a program is compiled in stan- dard mode, reference errors are not reported by the compiler. Instead, when the pro- gram runs in the debugger version of a Flash runtime, reference errors manifest as runtime exceptions. By contrast, when a program is compiled in strict mode, refer- ences to nonexistent variables or methods made through typed expressions are reported by the compiler, and cause compilation to fail. For example, the following code creates a variable, pet, of type VirtualPet, and assigns that variable an instance of the VirtualPet class: var pet:VirtualPet = new VirtualPet("Stan"); Next, the following code attempts to access a nonexistent method, eatt( ), through the typed variable pet: pet.eatt(new Sushi( )); In standard mode, the preceding code compiles, but generates a runtime reference error. In strict mode, the preceding code generates the following compile-time reference error and fails to compile. 1061: Call to a possibly undefined method eatt through a reference with static type zoo:VirtualPet. Service with a smile. 146 | Chapter 8: Datatypes and Type Checking Note, however, that the compiler does not report reference errors made through untyped expressions. Furthermore, references to nonexistent variables and methods made through instances of dynamic classes (such as Object) do not generate refer- ence errors of any kind; instead, such references yield the value undefined. For more information on dynamic classes, see Chapter 15. Here’s a type-annotation bonus: in FlexBuilder and the Flash author- ing tool, type annotations for variables, parameters, and return values activate code hints. A code hint is a handy pop-up menu that lists the properties and methods of objects as you write them in your code. Casting In the preceding section, we learned that in strict mode, the compiler reports refer- ence errors at compiletime. To detect reference errors, the compiler relies on type annotations. For example, suppose the compiler encounters a method reference made through a typed variable. To determine whether the reference is valid, the com- piler checks for the method’s definition in the class or interface specified by the vari- able’s type annotation. If the class or interface does not define the referenced method, the compiler generates a reference error. Notice that it is the class or interface specified by the type annota- tion—not the actual class of the value—that determines whether the reference error occurs. Consider the following code, in which the hasWorm( ) method is invoked on an Apple object through a variable of type Food: var meal:Food = new Apple( ); meal.hasWorm(); // Attempt to call hasWorm( ) on meal When compiling the preceding code in strict mode, the compiler must decide whether the hasWorm( ) method can be invoked on meal’s value. To do so, the com- piler checks to see whether the Food class (i.e., the class specified by meal’s type annotation) defines hasWorm( ). The Food class defines no such method, so the com- piler generates a reference error. Of course, by looking at the code, we know that meal’s value (an Apple object) supports the hasWorm( ) method. But compiler doesn’t. ActionScript must wait until runtime to learn that the variable’s value is actually an Apple object. Solution? Use a cast operation to force the compiler to allow the preceding hasWorm( ) invocation. A cast operation tells the compiler to treat a given expression as though it belongs to a specified datatype. A cast operation has the following generalized form: type(expression) Casting | 147 In the preceding code, type is any datatype, and expression is any expression. The operation is said to “cast the expression to the specified type.” For example, the fol- lowing code casts the expression meal to the Apple datatype before invoking hasWorm( ) on meal’s value: Apple(meal).hasWorm( ) No matter what the actual value of meal, the compiler believes that the datatype of the expression meal is Apple. Therefore, when deciding whether the hasWorm( ) method can be invoked on meal’s value, the compiler checks to see whether the Apple class—not the Food class—defines hasWorm( ). The Apple class does define hasWorm( ), so the compiler generates no errors. However, a cast operation is not merely a compile-time mechanism; it also has a run- time behavior. At runtime, if the expression resolves to an object that belongs to the specified type, then ActionScript simply returns that object. But if the expression resolves to an object that does not belong to the specified type, then the cast opera- tion has one of two results. If the specified type is not a primitive type, the cast oper- ation causes a runtime error; otherwise, the object is converted to the specified type (according to the rules listed in the section “Conversion to Primitive Types”) and the converted value is returned. For example, in the following code, the runtime value of meal belongs to the Apple datatype, so the cast operation on line 2 simply returns the Apple object referenced by meal: var meal:Food = new Apple( ); Apple(meal); // At runtime, returns the Apple object By comparison, in the following code, the runtime value of meal does not belong to the VirtualPet datatype, and VirtualPet is not a primitive type, so the cast operation on line 2 causes a type error: var meal:Food = new Apple( ); VirtualPet(meal); // At runtime, causes a type error Finally, in the following code, the runtime value of meal does not belong to the Boolean datatype, but Boolean is a primitive type, so the cast operation on line 2 converts meal’s value to the specified type, and returns the result of that conversion (true): var meal:Food = new Apple( ); Boolean(meal); // At runtime, returns the value true Avoiding Unwanted Type Mismatch Errors So far, we’ve learned that cast operations can be used to avoid unwanted compile- time reference errors. Similarly, cast operations can be used to avoid unwanted type- mismatch errors. 148 | Chapter 8: Datatypes and Type Checking As an example, imagine a program that converts a supplied Fahrenheit temperature to Celsius. The value of the Fahrenheit temperature is entered into a text field, which is represented by an instance of the built-in TextField class. To retrieve the input value, we access the text variable of the TextField instance, as shown in the follow- ing code: var fahrenheit:Number = inputField.text; As it stands, the preceding code causes a type mismatch error because the text vari- able’s datatype is String. To avoid that error, we use a cast operation, as follows: var fahrenheit:Number = Number(inputField.text); At runtime, the preceding cast converts the string value in inputField.text to a Number that is then assigned to fahrenheit. Upcasting and Downcasting Casting an object to one of its supertypes (superclass or superinterface) is known as an upcast. For example, the following operation is considered an upcast because Food is a supertype of Apple: Food(new Apple( )) Conversely, casting an object to one of its subtypes (subclass or subinterface) is known as a downcast because it casts the object’s type to a type further down the type hierarchy. The following operation is considered a downcast because Apple is a subtype of Food: Apple(new Food( )) An upcast is said to “widen” the object’s type because a supertype is more general- ized than its subtype. A downcast is said to “narrow” the object’s type because a sub- type is more specialized than its supertype. An upcast is also described as a safe cast because it never generates a runtime error. As we learned earlier, an instance of a subtype can always be safely treated as an instance of any of its supertypes because it is guaranteed (through inheritance) to have all of its supertypes’ non-private instance methods and variables. Conversely, a downcast is described as an unsafe cast because it has the potential to cause a runtime error. To guarantee that a downcast operation will not generate a runtime error, we must first check whether the object in question is actually an instance of the target datatype before performing the cast. To check the datatype of an object, we use the is operator, which has the following form: expression is type In the preceding code, expression is any expression, and type is any class or inter- face (and must not be undefined or null). An is operation returns the value true if the specified expression belongs to the specified type; otherwise, it returns false. Casting | 149 The following code uses the is operator to guarantee that a downcast operation will not generate a runtime error: var apple:Food = new Apple( ); if (apple is Apple) { Apple(apple).hasWorm( ); } In the preceding code, the statement block of the conditional statement will execute only if the variable apple refers to an object belonging to the Apple type. Hence, the cast operation Apple(apple) can never generate an error because it executes only when apple’s value belongs to the Apple type. Using the as Operator to Cast to Date and Array For legacy reasons, the cast syntaxdescribed in the preceding sections cannot be used to cast a value to the built-in Date or Array classes. The result of the expression Date(someValue) is identical to new Date().toString( ) (which returns a string repre- senting the current time). The result of the expression Array(someValue) is identical to new Array(someValue) (which creates a new Array object with someValue as its first element). To cast an expression to either the Date class or the Array classes, we use the as operator, which has the same behavior as a cast operation, except that it returns the value null in all cases where a cast operation would generate a runtime error. An as operation has the following form: expression as type In the preceding code, expression is any expression, and type is any class or inter- face (and must not be undefined or null). An as operation returns the value of expression if the specified expression belongs to the specified type; otherwise, it returns null. For example, in the following code, the expression (meal as Apple) has the same result as the cast operation Apple(meal): var meal:Food = new Apple( ); (meal as Apple).hasWorm( ); The following code uses the as operator to “cast” an Array object to the Array datatype so it can be assigned to a variable of type Array. public function output (msg:Object):void { if (msg is String) { trace(msg); } if (msg is Array) { var arr:Array = msg as Array; // "Cast" to Array here trace(arr.join("\n")); } } 150 | Chapter 8: Datatypes and Type Checking The following code shows the result of passing an example Array object to output( ): var numbers:Array = [1,2,3] output(numbers); // Output: 1 2 3 Conversion to Primitive Types In the preceding section we learned that when an expression is cast to a primitive type to which it does not belong, then that expression is converted to the specified type. For example, consider the following code, which casts a Date object to the primitive datatype Boolean: Boolean(new Date( )) Because Boolean is a primitive type, and the Date object does not belong to the Boolean type, ActionScript converts the Date object to the Boolean type. The result is the Boolean value true (see Table 8-5). Cast operations are sometimes used not to tell the compiler the type of a given expression but to convert that expression to a primitive datatype. A cast operation can convert any value to a particular primitive type. For example, the following code converts a floating-point number (a number with a fractional value) to an integer (a number with no fractional value): int(4.93) The result of the preceding cast operation is the integer 4. Likewise, the following code converts the Boolean value true to the integer 1, and the Boolean value false to the integer 0: int(true); // Yields 1 int(false); // Yields 0 The preceding technique might be used to reduce the size of data transmitted to a server when submitting a series of Boolean options. Table 8-1 shows the results of converting various datatypes to the Number type. Conversion to Primitive Types | 151 Table 8-2 shows the results of converting various datatypes to the int type. Table 8-3 shows the results of converting various datatypes to the uint type. Table 8-1. Conversion to Number Original data Result after conversion undefined NaN (the special numeric value “Not a Number,” which represents invalid numeric data). null 0 int The same number uint The same number Boolean 1 if the original value is true; 0 if the original value is false Numeric string Equivalent numeric value if string is composed only of base-10 or base-16 numbers, whitespace, exponent, decimal point, plus sign, or minus sign (e.g., “-1.485e2” becomes -148.5) Empty string 0 “Infinity” Infinity “-Infinity” -Infinity Other strings NaN Object NaN Table 8-2. Conversion to int Original data Result after conversion undefined 0 null 0 Number or uint An integer in the range -231 through 231-1, out of range values are brought into range using the algorithm listed in section 9.5 of the Standard ECMA-262, Third Edition Boolean 1 if the original value is true; 0 if the original value is false Numeric string Equivalent numeric value, converted to signed-integer format Empty string 0 “Infinity” 0 “-Infinity” 0 Other strings 0 Object 0 Table 8-3. Conversion to uint Original data Result after conversion undefined 0 null 0 Number or int An integer in the range 0 through 231-1, out of range values are brought into range using the algo- rithm listed in section 9.6 of the Standard ECMA-262, Third Edition Boolean 1 if the original value is true; 0 if the original value is false 152 | Chapter 8: Datatypes and Type Checking Table 8-4 shows the results of converting various datatypes to the String type. Table 8-5 shows the results of converting various datatypes to the Boolean type. Numeric string Equivalent numeric value, converted to unsigned-integer format Empty string 0 “Infinity” 0 “-Infinity” 0 Other strings 0 Object 0 Table 8-4. Conversion to String Original data Result after conversion undefined “undefined” null “null” Boolean “true” if the original value was true; “false” if the original value was false. NaN “NaN” 0 “0” Infinity “Infinity” -Infinity “-Infinity” Other numeric value String equivalent of the number. For example, 944.345 becomes “944.345”. Object The value that results from callingtoString( )on the object. By default, thetoString( )method of an object returns “[object className]”, where className is the object’s class. The toString( ) method can be overridden to return a more useful result. For example, toString( ) of a Date object returns the time in human-readable format, such as: “Sun May 14 11:38:10 EDT 2000”), while toString( ) of an Array object returns comma-separated list of element values. Table 8-5. Conversion to Boolean Original data Result after conversion undefined false null false NaN false 0 false Infinity true -Infinity true Other numeric value true Nonempty string true Empty string (“”) false Object true Table 8-3. Conversion to uint (continued) Original data Result after conversion null and undefined | 153 Default Variable Values When a variable is declared without a type annotation and without an initial value, then its initial value is automatically set to the value undefined (the sole value of the datatype void ). When a variable is declared with a type annotation but no initial value, then its initial value is automatically set to a default value for its specified datatype. Table 8-6 lists the default values, by datatype, for variables in ActionScript. null and undefined Earlier we learned that, the Null and void datatypes each include a single value only—null and undefined, respectively. Now that we have studied datatypes and type annotations, let’s consider the difference between those two values. Both null and undefined conceptually represent the absence of data. The null value represents the absence of data for variables, parameters, and return values with a specified type annotation set to anything but Boolean, int, uint, and Number. For example, the following code creates a typed instance variable, pet, of type VirtualPet. Before the variable is explicitly assigned a value in the program, its value is null. package { import flash.display.Sprite; import zoo.*; public class VirtualZoo extends Sprite { private var pet:VirtualPet; public function VirtualZoo ( ) { trace(pet); // Displays: null } } } By contrast, the undefined value represents the absence of data for variables, parameters, and return values without a specified type annotation. For example, the Table 8-6. Default variable values Datatype Default value String null Boolean false int 0 uint 0 Number NaN All other types null 154 | Chapter 8: Datatypes and Type Checking following code creates an object with two dynamic instance variables, city and country. When assigning the country variable an initial value, the code uses undefined to indicate that country does not yet have a meaningful value. var info = new Object( ); info.city = "Toronto"; info.country = undefined; The undefined value also represents the complete absence of a variable or method on an object whose class is defined as dynamic. For example, the following attempt to access a nonexistent variable through the object referenced by info yields undefined: trace(info.language); // Displays: undefined We’ll learn more about ActionScript’s dynamic features and the undefined value in Chapter 15. Datatypes in the Virtual Zoo Now that we’ve learned all about datatypes, let’s add type annotations to our virtual zoo program. Example 8-1 shows the updated code for the VirtualZoo class, the pro- gram’s main class. Example 8-2 shows the code for the VirtualPet class, whose instances represent pets in the zoo. Notice the cast operation in the eat( ) method, discussed in the earlier sec- tion “Upcasting and Downcasting.” Example 8-1. The VirtualZoo class package { import flash.display.Sprite; import zoo.*; public class VirtualZoo extends Sprite { private var pet:VirtualPet; public function VirtualZoo ( ) { pet = new VirtualPet("Stan"); pet.eat(new Apple( )); pet.eat(new Sushi( )); } } } Example 8-2. The VirtualPet class package zoo { import flash.utils.setInterval; import flash.utils.clearInterval; public class VirtualPet { private static var maxNameLength:int = 20; Datatypes in the Virtual Zoo | 155 private static var maxCalories:int = 2000; private static var caloriesPerSecond:int = 100; private var petName:String; private var currentCalories:int = VirtualPet.maxCalories/2; private var digestIntervalID:int; public function VirtualPet (name:String):void { setName(name); digestIntervalID = setInterval(digest, 1000); } public function eat (foodItem:Food):void { if (currentCalories == 0) { trace(getName( ) + " is dead. You can't feed it."); return; } if (foodItem is Apple) { // Note the cast to Apple if (Apple(foodItem).hasWorm( )) { trace("The " + foodItem.getName() + " had a worm. " + getName( ) + " didn't eat it."); return; } } var newCurrentCalories:int = currentCalories + foodItem.getCalories( ); if (newCurrentCalories > VirtualPet.maxCalories) { currentCalories = VirtualPet.maxCalories; } else { currentCalories = newCurrentCalories; } trace(getName() + " ate some " + foodItem.getName( ) + "." + " It now has " + currentCalories + " calories remaining."); } public function getHunger ( ):Number { return currentCalories / VirtualPet.maxCalories; } public function setName (newName:String):void { // If the proposed new name has more than maxNameLength characters... if (newName.length > VirtualPet.maxNameLength) { // ...truncate it newName = newName.substr(0, VirtualPet.maxNameLength); } else if (newName == "") { // ...otherwise, if the proposed new name is an empty string, // then terminate this method without changing petName return; } Example 8-2. The VirtualPet class (continued) 156 | Chapter 8: Datatypes and Type Checking Example 8-3 shows the code for the Food class, the superclass of the various types of food that pets eat. // Assign the new, validated name to petName petName = newName; } public function getName ( ):String { return petName; } private function digest ( ):void { // If digesting more calories would leave the pet's currentCalories at // 0 or less... if (currentCalories - VirtualPet.caloriesPerSecond <= 0) { // ...stop the interval from calling digest( ) clearInterval(digestIntervalID); // Then give the pet an empty stomach currentCalories = 0; // And report the pet's death trace(getName( ) + " has died."); } else { // ...otherwise, digest the stipulated number of calories currentCalories -= VirtualPet.caloriesPerSecond; // And report the pet's new status trace(getName( ) + " digested some food. It now has " + currentCalories + " calories remaining."); } } } } Example 8-3. The Food class package zoo { public class Food { private var calories:int; private var name:String; public function Food (initialCalories:int) { setCalories(initialCalories); } public function getCalories ( ):int { return calories; } public function setCalories (newCalories:int):void { calories = newCalories; } Example 8-2. The VirtualPet class (continued) Datatypes in the Virtual Zoo | 157 Example 8-4 shows the code for the Apple class, which represents a specific type of food that pets eat. Finally, Example 8-5 shows the code for the Sushi class, which represents a specific type of food that pets eat. public function getName ( ):String { return name; } public function setName (newName:String):void { name = newName; } } } Example 8-4. The Apple class package zoo { public class Apple extends Food { private static var DEFAULT_CALORIES:int = 100; private var wormInApple:Boolean; public function Apple (initialCalories:int = 0) { if (initialCalories <= 0) { initialCalories = Apple.DEFAULT_CALORIES; } super(initialCalories); wormInApple = Math.random( ) >= .5; setName("Apple"); } public function hasWorm ( ):Boolean { return wormInApple; } } } Example 8-5. The Sushi class package zoo { public class Sushi extends Food { private static var DEFAULT_CALORIES:int = 500; public function Sushi (initialCalories:int = 0) { if (initialCalories <= 0) { initialCalories = Sushi.DEFAULT_CALORIES; } super(initialCalories); Example 8-3. The Food class (continued) 158 | Chapter 8: Datatypes and Type Checking More Datatype Study Coming Up In this chapter, we learned how to use datatypes to help identify and resolve poten- tial problems in a program. In the next chapter, we’ll conclude our general explora- tion of datatypes by studying interfaces. Like classes, interfaces are used to create custom datatypes. setName("Sushi"); } } } Example 8-5. The Sushi class (continued) 159 Chapter 9 CHAPTER 9 Interfaces10 An interface is an ActionScript language construct that defines a new datatype, much as a class defines a datatype. However, whereas a class both defines a datatype and provides the implementation for it, an interface defines a datatype in abstract terms only, and provides no implementation for that datatype. That is, a class doesn’t just declare a bunch of methods and variables, it also supplies concrete behavior; the method bodies and variable values that make the class actually do something. An interface, instead of providing its own implementation, is adopted by one or more classes that agree to provide the implementation. Instances of a class that provides an implementation for an interface belong both to the class’s datatype and to the datatype defined by the interface. As a member of multiple datatypes, the instances can then play multiple roles in an application. Don’t confuse the term interface, as discussed in this chapter, with other uses of the word. In this chapter, “interface” refers to an Action- Script language construct, not a graphical user interface (GUI) or the public API of a class, sometimes also called an interface in general object-oriented programming theory. Unless you’re familiar with interfaces already, theoretical descriptions of them can be hard to follow, so let’s dive right into an example. The Case for Interfaces Suppose we’re creating a logging class, Logger, that reports status messages (“log entries”) for a program as it runs. Many classes receive the Logger’s status messages and respond to them in different ways. For example, one class, LogUI, displays log messages on screen; another class, LiveLog, alerts a live support technician via a net- worked administration tool; yet another class, LogTracker, adds log messages to a database for statistics tracking. To receive log messages, each class defines an update( ) method. To send a message to objects of each interested class, the Logger class invokes the update( ) method. 160 | Chapter 9: Interfaces That all seems logical enough so far, but what happens if we forget to define the update( ) method in the LogUI class? The status message will be sent, but LogUI objects won’t receive it. We need a way to guarantee that each log recipient defines the update( ) method. To make that guarantee, suppose we add a new requirement to our program: any object that wants to receive log messages from Logger must be an instance of a generic LogRecipient class (which we’ll provide) or an instance of one of LogRecipient’s subclasses. In the LogRecipient class, we implement the update( ) method in a generic way—by simply displaying the log message using trace( ): public class LogRecipient { public function update (msg:String):void { trace(msg); } } Now any class that wishes to receive log messages from Logger simply extends LogRecipient and if specialized behavior is wanted, overrides LogRecipient’s update( ) method, providing the desired behavior. For example, the following class, LogTracker, extends LogRecipient and overrides update( ), providing database- specific behavior: public class LogTracker extends LogRecipient { // Override LogRecipient's update( ) override public function update (msg:String):void { // Send problem report to database. Code not shown... } } Back in the Logger class, we define a method, addRecipient( ), that registers an object to receive log messages. The basic code for addRecipient( ) follows. Notice that only instances of the LogRecipient class and its subclasses can be passed to addRecipient( ): public class Logger { public function addRecipient (lr:LogRecipient):Boolean { // Code here should register lr to receive status messages, // and return a Boolean value indicating whether registration // succeeded (code not shown). } } If an object passed to addRecipient( ) is not of type LogRecipient, then the compiler generates a type mismatch error. If the object is an instance of a LogRecipient sub- class that doesn’t implement update( ), at least the generic update( ) (defined by LogRecipient) will execute. Sounds reasonable, right? Almost. But there’s a problem. What if a class wishing to receive events from LogRecipient already extends another class? For example, sup- pose the LogUI class extends flash.display.Sprite: public class LogUI extends Sprite { Interfaces and Multidatatype Classes | 161 public function update (msg:String):void { // Display status message on screen, code not shown... } } In ActionScript, a single class cannot extend more than one class. The LogUI class already extends Sprite, so it can’t also extend LogRecipient. Therefore, instances of LogUI can’t register to receive status messages from Logger. What we really need in this situation is a way to indicate that LogUI instances actually belong to two datatypes: LogUI and LogRecipient. Enter...interfaces! Interfaces and Multidatatype Classes In the preceding section, we created the LogRecipient datatype by creating a LogRecipient class. That approach forces every Logger message-recipient to be an instance of either LogRecipient or a LogRecipient subclass. To loosen that restriction, we can define the LogRecipient datatype by creating a LogRecipient interface rather than a LogRecipient class. That way, instances of any class that formally agrees to provide an implementation for update( ) can register for log messages. Let’s see how this works. Syntactically, an interface is simply a list of methods. For example, the following code creates an interface named LogRecipient that contains a single method, update( ). (Notice that, like classes, interfaces can be defined as either public or internal.) public interface LogRecipient { function update(msg:String):void; } Once an interface has been defined, any number of classes can use the keyword implements to enter into an agreement with it, promising to define the methods it contains. Once such a promise has been made, the class’s instances are considered members of both the class’s datatype and the interface’s datatype. For example, to indicate that the LogUI class agrees to define the method update( ) (defined by the LogRecipient interface), we use the following code: class LogUI extends Sprite implements LogRecipient { public function update (msg:String):void { // Display status message on screen, code not shown... } } Instead of extending the LogRecipient class, the LogUI class extends Sprite and implements the LogRecipient interface. Because LogUI implements LogRecipient,it must define an update( ) method. Otherwise, the compiler generates the following error: Interface method update in namespace LogRecipient not implemented by class LogUI. 162 | Chapter 9: Interfaces Because LogUI promises to implement LogRecipient’s methods, LogUI instances can be used anywhere the LogRecipient datatype is required. Instances of LogUI effec- tively belong to two datatypes: LogUI and LogRecipient. Thus, despite the fact that LogUI extends Sprite, LogUI instances still belong to the LogRecipient type and can be passed safely to Logger’s addRecipient( ) method. (Wow, Ron, that’s amazing! It’s a pasta maker and a juicer!) Compiler errors are the key to the entire interface system. They guarantee that a class lives up to its implementation promises, which allows external code to use it with the confidence that it will behave as required. That confidence is particularly important when designing an application that will be extended by another developer or used by third parties. Now that we have a general idea of what interfaces are and how they’re used, let’s get down to some syntax details. Interface Syntax and Use Recall that an interface defines a new datatype without implementing any of the methods of that datatype. Thus, to create an interface, we use the following syntax: interface SomeName { function method1 (param1:datatype,...paramn:datatype):returnType; function method2 (param1:datatype,...paramn:datatype):returnType; ... function methodn (param1:datatype,...paramn:datatype):returnType; } where SomeName is the name of the interface, method1, ...methodn are the methods in the interface, param1:datatype, ...paramn:datatype are the parameters of the meth- ods, and returnType is the datatype of each method’s return value. In interfaces, method declarations do not (and must not) include curly braces. The following method declaration causes a compile-time error in an interface because it includes curly braces: function method1 (param:datatype):returnType { } The error generated is: Methods defined in an interface must not have a body. All methods declared in an interface must not include an access-control modifier. Variable definitions are not allowed in ActionScript interfaces; neither can interface definitions be nested. However, interfaces can include get and set methods, which can be used to simulate variables (from the perspective of the code using the meth- ods). Like class definitions, interface definitions can be placed directly within a package statement or outside of any package statement, but nowhere else. Interface Syntax and Use | 163 As we saw in the preceding section, a class that wishes to adopt an interface’s datatype must agree to implement that interface’s methods. To form such an agree- ment, the class uses the implements keyword, which has the following syntax: class SomeName implements SomeInterface { } In the preceding code, SomeName is the name of the class that promises to implement SomeInterface’s methods, and SomeInterface is the name of the interface. The SomeName class is said to “implement the SomeInterface interface.” Note that implements must always come after any extends clause that might also be present. Furthermore, if you specify a class instead of an interface after the implements key- word, the compiler generates this error: An interface can only extend other interfaces, but ClassName is a class. The class SomeName must implement all methods defined by SomeInterface, otherwise a compile-time error such as the following occurs: Interface method methodName in namespace InterfaceName not implemented by class ClassName. The implementing class’s method definitions must be public and must match the interface’s method definitions exactly, including number of parameters, parameter types, and return type. If any of those aspects differs between the interface and the implementing class, the compiler generates the following error: Interface method methodName in namespace InterfaceName is implemented with an incompatible signature in class ClassName. A class can legally implement more than one interface by separating interface names with commas, as follows: class SomeName implements SomeInterface, SomeOtherInterface { } in which case, instances of the class SomeName belongs to all three of the following datatypes: SomeName, SomeInterface, and SomeOtherInterface. If a class implements two interfaces that define a method by the same name, but with different signatures (i.e., method’s name, parameter list, and return type), the compiler generates an error indicating that one of the methods was not implemented properly. If, on the other hand, a class implements two interfaces that define a method by the same name and with the exact same signature, no error occurs. The real question is whether the class can provide the services required by both interfaces within a single method definition. In most cases, the answer is no. Once an interface has been implemented by one or more classes, add- ing new methods to it will cause compile-time errors in those imple- menting classes (because the classes won’t define the new methods)! Hence, you should think carefully about the methods you want in an interface and be sure you’re confident in your application’s design before you commit it to code. 164 | Chapter 9: Interfaces If a class declares that it implements an interface, but that interface cannot be found by the compiler, the following error occurs: Interface InterfaceName was not found. Interface Naming Conventions Like classes, interfaces should be named with an initial capital letter so they’re easy to identify as datatypes. Most interfaces are named after the additional ability they describe. For example, suppose an application contains a series of classes that repre- sent visual objects. Some of the objects can be repositioned; others cannot. In our design, objects that can be repositioned must implement an interface named Moveable. Here is a theoretical ProductIcon class that implements Moveable: public class ProductIcon implements Moveable { public function getPosition ( ):Point { } public function setPosition (pos:Point):void { } } The interface name, Moveable, indicates the specific capability that the interface adds to a class. An object might be a piece of clip art or a block of text, but if it imple- ments Moveable, it can be repositioned. Other similar names might be Storable, Killable,orSerializable. Some developers also preface interface names with an “I,” as in IMoveable, IKillable, and ISerializable. Interface Inheritance As with classes, an interface can use the extends keyword to inherit from another interface. For example, the following code shows an interface, IntA, that extends another interface, IntB. In this setup, interface IntB is known as the subinterface, and interface IntA is known as the superinterface. public interface IntA { function methodA ( ):void; } public interface IntB extends IntA { function methodB ( ):void; } Classes that implement interface IntB must provide definitions for both methodA( ) and methodB( ). Interface inheritance lets us define a type hierarchy much as we would with class inheritance, but without accompanying method implementations. ActionScript interfaces also support multiple interface inheritance; that is, an inter- face can extend more than one interface. For example, consider the following three interface definitions: public interface IntC { function methodC ( ):void; } Another Multiple-Type Example | 165 public interface IntD { function methodD ( ):void; } public interface IntE extends IntC, IntD { function methodE ( ):void; } Because IntE extends both IntC and IntD, classes that implement interface IntE must provide definitions for methodC( ), methodD( ), and methodE( ). Marker Interfaces Interfaces need not contain any methods at all to be useful. Occasionally, empty interfaces, called marker interfaces, are used to “mark” (designate) a class as having some feature. Requirements for the marked classes (classes implementing the marker interface) are provided by the documentation for the marker interface. For example, the Flash runtime API includes a marker interface, IBitmapDrawable, which desig- nates a class as eligible for drawing into a BitmapData object. The BitmapData class will draw only those classes that implement IBitmapDrawable (even though IBitmapDrawable does not actually define any methods). The IBitmapDrawable interface is simply used to “approve” a given class for drawing into a bitmap. Here’s the source code for the IBitmapDrawable interface: package flash.display { interface IBitmapDrawable { } } Another Multiple-Type Example In our earlier logging example, we learned that a class can inherit from another class while also implementing an interface. Instances of the subclass belong to both the superclass’s datatype and the interface’s datatype. For example, instances of the ear- lier LogUI class belonged to both the Sprite and LogRecipient datatypes because LogUI inherited from Sprite and implemented LogRecipient. Let’s take a closer look at this important architectural structure with a new example. The following discussion requires a prior knowledge of arrays (ordered lists of values), which we haven’t covered yet. If you are new to arrays, you should skip this section for now and return to it after you have read Chapter 11. Suppose we’re creating an application that stores objects on a server via a server-side script. Each stored object’s class is responsible for providing a method, serialize( ), that can return a string-representation of its instances. The string representation is used to reconstitute a given object from scratch. 166 | Chapter 9: Interfaces One of the classes in the application is a simple Rectangle class with width, height, fillColor, and lineColor instance variables. To represent Rectangle objects as strings, the Rectangle class implements a serialize( ) method that returns a string of the following format: "width=value|height=value|fillColor=value|lineColor=value" To store a given Rectangle object on the server, we invoke serialize( ) on the object and send the resulting string to our server-side script. Later, we can retrieve that string and use it to create a new Rectangle instance matching the original’s size and colors. To keep things simple for this example, we’ll presume that every stored object in the application must store only variable names and values. We’ll also presume that no variable values are, themselves, objects that would need serialization. When the time comes to save the state of our application, an instance of a custom StorageManager class performs the following tasks: • Gathers objects for storage • Converts each object to a string (via serialize( )) • Transfers the objects to disk In order to guarantee that every stored object can be serialized (i.e., converted to a string), the StorageManager class rejects any instances of classes that do not belong to the Serializable datatype. Here’s an excerpt from the StorageManager class that shows the method an object uses to register for storage—addObject( ) (notice that only instances belonging to the Serializable type can be passed to addObject( )): package { public class StorageManager { public function addObject (o:Serializable):void { } } } The Serializable datatype is defined by the interface Serializable, which contains a single method, serialize( ), as follows: package { public interface Serializable { function serialize( ):String; } } To handle the serialization process, we create a class, Serializer, which implements Serializable. The Serializer class provides the following general methods for serializ- ing any object: setSerializationObj( ) Specifies which object to serialize setSerializationVars( ) Specifies which of the object’s variables should be serialized Another Multiple-Type Example | 167 setRecordSeparator( ) Specifies the string to use as a separator between variables serialize( ) Returns a string representing the object Here’s the class listing for Serializer: package { public class Serializer implements Serializable { private var serializationVars:Array; private var serializationObj:Serializable; private var recordSeparator:String; public function Serializer ( ) { setSerializationObj(this); } public function setSerializationVars (vars:Array):void { serializationVars = vars; } public function setSerializationObj (obj:Serializable):void { serializationObj = obj; } public function setRecordSeparator (rs:String):void { recordSeparator = rs; } public function serialize ( ):String { var s:String = ""; // Notice that the loop counts down to 0, and performs the // iterator update (decrementing i) within the loop's test expression for (var i:int = serializationVars.length; --i >= 0; ) { s += serializationVars[i] + "=" + String(serializationObj[serializationVars[i]]); if (i > 0) { s += recordSeparator; } } return s; } } } To use the Serializer class’s serialization services, a class can simply extend Serializer. By extending Serializer directly, the extending class inherits both the Serializable interface and the Serializer class’s implementation of that interface. Notice the general structure of our serialization system: Serializer implements Serializable, providing a generalized implementation for other classes to use via inheritance. But classes can still choose to implement Serializable directly, supplying their own custom behavior for the serialize( ) method. 168 | Chapter 9: Interfaces For example, the following code shows a Point class that defines x and y variables, which need to be serialized. The Point class extends Serializer and uses Serializer’s services directly. package { public class Point extends Serializer { public var x:Number; public var y:Number; public function Point (x:Number, y:Number) { super( ); setRecordSeparator(","); setSerializationVars(["x", "y"]); this.x = x; this.y = y; } } } Code that wishes to save a Point instance to disk simply calls serialize( ) on that instance, as follows: var p:Point = new Point(5, 6); trace(p.serialize( )); // Displays: y=6,x=5 Notice that the Point class does not implement Serializable directly. It extends Serializer, which in turn implements Serializable. The Point class does not extend any other class, so it’s free to extend Serializer. How- ever, if a class wants to use Serializer but already extends another class, it must use composition instead of inheritance. That is, rather than extending Serializer, the class implements Serializable directly, stores a Serializer object in an instance vari- able, and forwards serialize( ) method calls to that object. For example, here’s the Rectangle class mentioned earlier. It extends a Shape class but uses Serializer via composition (refer specifically to the sections in bold): // The Shape superclass package { public class Shape { public var fillColor:uint = 0xFFFFFF; public var lineColor:uint = 0; public function Shape (fillColor:uint, lineColor:uint) { this.fillColor = fillColor; this.lineColor = lineColor; } } } // The Rectangle class package { // The Rectangle subclass implements Serializable directly Another Multiple-Type Example | 169 public class Rectangle extends Shape implements Serializable { public var width:Number = 0; public var height:Number = 0; private var serializer:Serializer; public function Rectangle (fillColor:uint, lineColor:uint) { super(fillColor, lineColor) // Here is where the composition takes place serializer = new Serializer( ); serializer.setRecordSeparator("|"); serializer.setSerializationVars(["height", "width", "fillColor", "lineColor"]); serializer.setSerializationObj(this); } public function setSize (w:Number, h:Number):void { width = w; height = h; } public function getArea ( ):Number { return width * height; } public function serialize ( ):String { // Here is where the Rectangle class forwards the serialize( ) // invocation to the Serializer instance stored in serializer return serializer.serialize( ); } } } As with the Point class, code that wishes to store a Rectangle instance simply invokes serialize( ) on that instance. Through composition, the invocation is forwarded to the Serializer instance stored by the Rectangle class. Here is an example of its use: var r:Rectangle = new Rectangle(0xFF0000, 0x0000FF); r.setSize(10, 15); // Displays: lineColor=255|fillColor=16711680|width=10|height=15 trace(r.serialize( )); If a class would rather implement its own custom serialize( ) method instead of using the generic one provided by Serializer, then the class simply implements the Serializable interface directly, providing the serialize( ) method definition and body itself. Separating the Serializable datatype’s interface from its implementation allows any class to flexibly choose from among the following options when providing an imple- mentation for the serialize( ) method: • Extend Serializer • Use Serializer via composition • Provide its own serialize( ) method directly 170 | Chapter 9: Interfaces If the class does not already extend another class, it can extend Serializer (this option involves the least work). If the class already extends another class, it can still use Serializer via composition (this option is the most flexible). Finally, if the class needs its own special serialization routine, it can implement Serializable directly (this option involves the most work but may be required by the situation at hand). The flexibility of the preceding structure led Sun Microsystems to formally recom- mend that, in a Java application, any class that is expected to be subclassed should be an implementation of an interface. As such, it can be subclassed directly, or it can be used via composition by a class that inherits from another class. Sun’s recommen- dation is also sensible for large-scale ActionScript applications. Figure 9-1 shows the generic structure of a datatype whose implementation can be used via either inheritance or composition. Figure 9-2 shows the structure of the specific Serializable, Point, and Rectangle example. Figure 9-1. Multiple datatype inheritance via interfaces SomeAbstractType (Abstract datatype definition) SomeConcreteType (Datatype implementation) SomeOtherType (Unrelated class) SomeConcreteSubtype (SomeAbstractType used via inheritance) SomeOtherSubtype (SomeAbstractType used via composition) SomeConcreteType Instance implements extendsextends implementsstores interface class More Essentials Coming | 171 More Essentials Coming Having covered classes, objects, inheritance, datatypes, and interfaces, we’ve now finished our study of the basic concepts of object-oriented programming. In the remainder of Part I, we’ll explore a variety of other fundamental topics in Action- Script. But the object-oriented concepts we’ve studied will never be far behind. Object-oriented programming is the true foundation of ActionScript. The concepts presented in remainder of this book will all build and rely upon that foundation. Up next, an overview of ActionScript’s statements and operators. Figure 9-2. Multiple datatype inheritance Serializable example Serializable (Abstract datatype definition) Serializer (Datatype implementation) Shape (Unrelated class) Point (Serializer used via inheritance) Rectangle (Serializer used via composition) Serializer Instance implements extendsextends implementsstores interface class 172 Chapter 10CHAPTER 10 Statements and Operators 11 This chapter provides a reference-style overview of ActionScript’s statements and operators—many of which we’ve already seen in this book. Rather than discussing each statement and operator in isolation, this book teaches the use of statements and operators in the context of other programming topics. Accordingly, this chapter lists many crossreferences to discussion and usage examples found elsewhere in this book. For information on operators not covered in this book, see Adobe’s Action- Script Language Reference. Statements Statements are one kind of directive, or basic program instruction, consisting of a key- word (command name reserved for use by the ActionScript language) and, typically, a supporting expression. Table 10-1 lists ActionScript’s statements, their syntax, and purpose. Table 10-1. ActionScript statements Statement Usage Description break break Aborts a loop or switch statement. See Chapter 2. case case expression: substatements Identifies a statement to be executed conditionally in a switch statement. See Chapter 2. continue continue; Skips the remaining statements in the current loop and begins the next iteration at the top of the loop. See Adobe documentation. default default: substatements Identifies the statement(s) to execute in a switch statement when the test expression does not match any case clauses. See Chapter 2. do-while do { substatements } while (expression); A variation of a while loop that ensures at least one iteration of the loop is performed. See Chapter 2. Statements | 173 for for (init; test; update) { statements } Executes a statement block repetitively (a for loop). It is synonymous with a while loop but places the loop initialization and update statements together with the test expression at the top of the loop. See Chapter 2. for-in for (variable in object) { statements } Enumerates the names of the dynamic instance variables of an object or an array’s elements. See Chapter 15. for-each-in for each (variableOrElementValue in object) { statements } Enumerates the values of an object’s dynamic instance variables or an array’s elements. See Chapter 15. if-else if-else if (expression) { substatements } else if (expression) { substatements } else { substatements } Executes one or more statements, based on a condition or a series of conditions. See Chapter 2. label label: statement label: statements Associates a statement with an identifier. Used with break or continue. See Adobe documentation. return return; return expression; Exits and optionally returns a value from a function. See Chapter 5. super super(arg1, arg2, ...argn) super.method(arg1, arg2, .. .argn) Invokes a superclass’s constructor method or overridden instance method. See Chapter 6. switch switch (expression) { substatements } Executes specified code, based on a condition or a series of conditions (alternative toif-elseif-else). See Chapter 2. throw throw expression Issues a runtime exception (error). See Chapter 13. try/catch/finally try { // Code that might // generate an exception } catch (error:ErrorType1){ // Error-handling code // for ErrorType1. } catch (error:ErrorTypeN){ // Error-handling code // for ErrorTypeN. } finally { // Code that always executes } Wraps a block of code to respond to potential run- time exceptions. See Chapter 13. while while (expression) { substatements } Executes a statement block repetitively (a while loop). See Chapter 2. with with (object) { substatements } Executes a statement block in the scope of a given object. See Chapter 16. Table 10-1. ActionScript statements (continued) Statement Usage Description 174 | Chapter 10: Statements and Operators Operators An operator is a symbol or keyword that manipulates, combines, or transforms data. For example, the following code uses the multiplication operator (*) to multiply 5 times 6: 5 * 6; Though each operator has its own specialized task, all operators share a number of general characteristics. Before we consider the operators individually, let’s see how they behave generally. Operators perform actions using the data values (operands) supplied. For example, in the operation 5*6, the numbers 5 and 6 are the operands of the multiplication operator (*). Operations can be combined to form complex expressions. For example: ((width * height) - (Math.PI * radius * radius)) / 2 When expressions become very large, consider using variables to hold interim results for both convenience and clarity. Remember to name your variables descriptively. For example, the following code has the same result as the preceding expression but is much easier to read: var radius:int = 10; var height:int = 25; var circleArea:Number = (Math.PI * radius * radius); var cylinderVolume:Number = circleArea * height; Number of Operands Operators are sometimes categorized according to how many operands they take (i.e., require or operate on). Some ActionScript operators take one operand, some take two, and one even takes three: –x // One operand x * y // Two operands (x == y) ? "true result" : "false result" // Three operands Single-operand operators are called unary operators; operators that take two operands are called binary operators; operators that take three operands are called ternary operators. For our purposes, we’ll look at operators according to what they do, not the number of operands they take. Operator Precedence Operators’ precedence determines which operation is performed first in an expres- sion with multiple operators. For example, when multiplication and addition occur in the same expression, multiplication is performed first: 4 + 5 * 6 // Yields 34, because 4 + 30 = 34 Operators | 175 The expression 4+5*6is evaluated as “4 plus the product of 5*6” because the * operator has higher precedence than the + operator. Similarly, when the less-than (<) and concatenation (+) operators occur in the same expression, concatenation is performed first. For example, suppose we want to com- pare two strings and then display the result of that comparison during debugging. Without knowing the precedence of the < and + operators, we might mistakenly use the following code: trace("result: " + "a" < "b"); Due precedence of the < and + operators, the preceding code yields the value: false whereas we were expecting it to yield: result: true To determine the result of the expression "result: " + "a" < "b", ActionScript per- forms the concatenation operation first (because + has a higher precedence than <). The result of concatenating “result: ” with “a” is a new string, “result: a”. Action- Script then compares that new string with “b”, which yields false because the first character in “result: a” is alphabetically greater than “b”. When in doubt, or to ensure a different order of operation, use parentheses, which have the highest precedence: "result: " + ("a" < "b") // Yields: "result: true" (4 + 5) * 6 // Yields 54, because 9 * 6 = 54 Even if not strictly necessary, parentheses can make a complicated expression more readable. The expression: x > y || y == z // x is greater than y, or y equals z may be difficult to comprehend without consulting a precedence table. It’s a lot eas- ier to read with parentheses added: (x > y) || (y == z) // Much better! The precedence of each operator is listed later in Table 10-2. Operator Associativity As we’ve just seen, operator precedence indicates the pecking order of operators: those with a higher precedence are executed before those with a lower precedence. But what happens when multiple operators occur together and have the same level of precedence? In such a case, we apply the rules of operator associativity, which indi- cate the direction of an operation. Operators are either left-associative (performed left to right) or right-associative (performed right to left). For example, consider this expression: b * c / d 176 | Chapter 10: Statements and Operators The * and / operators are left-associative, so the * operation on the left (b*c) is per- formed first. The preceding example is equivalent to: (b * c) / d In contrast, the = (assignment) operator is right-associative, so the expression: a = b = c = d says, “assign d to c, then assign c to b, then assign b to a,” as in: a = (b = (c = d)) Unary operators are right-associative; binary operators are left-associative, except for the assignment operators, which are right-associative. The conditional operator (?:) is also right-associative. Operator associativity is fairly intuitive, but if you’re getting an unexpected value from a complex expression, add extra parentheses to force the desired order of operations. For further information on operator associativity in ActionScript, see Adobe’s documentation. Datatypes and Operators The operands of most operators are typed. In strict mode, if a value used for an oper- and does not match that operand’s datatype, the compiler generates a compile-time error and refuses to compile the code. In standard mode, the code compiles, and at runtime, if the operand’s type is a primitive type, ActionScript converts the value to the operand’s datatype (according to the rules described in Chapter 8, in the section “Conversion to Primitive Types”). If the operand’s type is not a primitive type, ActionScript generates a runtime error. For example, in strict mode, the following code causes a type mismatch error because the datatype of the division (/) operator’s operands is Number, and the value "50" does not belong to the Number datatype: "50" / 10 In standard mode, the preceding code does not cause a compile-time error. Instead, at runtime, ActionScript converts the String value "50" to the Number datatype, yielding 50, and the entire expression has the value 5. To compile the preceding code without causing an error in strict mode, we must cast the String value to the required datatype, as follows: Number("50") / 10 Some operators’ operands are untyped, which allows the result of the operation to be determined at runtime based on the datatypes of the supplied values. The + opera- tor, for example, performs addition when used with two numeric operands, but it performs concatenation when either operand is a string. The datatypes of each operator’s operands are listed in Adobe’s ActionScript Lan- guage Reference. Operators | 177 Operator Overview Table 10-2 lists ActionScript’s operators, their precedence value, a brief description, and a typical example of their use. Operators with the highest precedence (at the top of the table) are executed first. Operators with the same precedence are performed in the order they appear in the expression, usually from left to right, unless the associa- tivity is right to left. Note that with the exception of the E4X operators, this book does not provide exhaustive reference information for ActionScript’s operators. For details on a spe- cific operator, consult Adobe’s ActionScript Language Reference. For information on the bitwise operators, see the article “Using Bitwise Operators in ActionScript” at http://www.moock.org/asdg/technotes/bitwise. 178 Table 10-2. ActionScript operators Operator Precedence Description Example . 15 Multiple uses: • Accesses a variable or method • Separates package names from class names and other package names • Accesses children of an XML or XMLList object (E4X) // Access a variable product.price // Reference a class flash.display.Sprite // Access an XML child element novel.TITLE [] 15 Multiple uses: • Initializes an array • Accesses an array element • Accesses a variable or method using any expression that yields a string • Accesses children or attributes of an XML or XMLList object (E4X) // Initialize an array ["apple", "orange", "pear"] // Access fourth element of an array list[3] // Access a variable product["price"] // Access an XML child element novel["TITLE"] () 15 Multiple uses: • Specifies a custom order of operations (precedence) • Invokes a function or method • Contains an E4X filtering predicate // Force addition before multiplication (5 + 4) * 2 // Invoke a function trace( ) // Filter an XMLList staff.*.(SALARY <= 35000) @ 15 Accesses XML attributes // Retrieve all attributes of novel novel.@* :: 15 Separates a qualifier namespace from a name // Qualify orange with namespace fruit fruit::orange .. 15 Accesses XML descendants // Retrieve all descendant elements // of loan named DIRECTOR loan..DIRECTOR {x:y} 15 Creates a new object and initializes its dynamic variables // Create an object with dynamic variables, // width and height {width:30, height:5} new 15 Creates an instance of a class // Create TextField instance new TextField( ) 179 < tag >< tag /> 15 Defines an XML element // Create an XML element named BOOK Essential ActionScript 3.0 x ++ 14 Adds one to x and returns x ’ s former value (postfix increment) // Increase i by 1, and return i i++ x –– 14 Subtracts one from x and returns x ’ s former value (postfix decrement) // Decrease i by 1, and return i i-- ++ x 14 Adds one to x and returns x ’ s new value (prefix increment) // Increase i by 1, and return the result ++i –– x 14 Subtracts one from x and returns x ’ s new value (prefix decrement) // Decrease i by 1, and return the result --i – 14 Switches the operand ’ s sign (positive becomes negative, and negative becomes positive) var a:int = 10; // Assign -10 to b var b:int = -b; ~ 14 Performs a bitwise NOT // Clear bit 2 of options options &= ~4; ! 14 Returns the Boolean opposite of its single operand // If under18's value is not true, // execute conditional body if (!under18) { trace("You can apply for a credit card") } delete 14 Multiple uses: • Removes the value of an array element • Removes an object ’ s dynamic instance variable • Removes an XML element or attribute // Create an array var genders:Array = ["male","female"] // Remove the first element's value delete genders[0]; // Create an object var o:Object = new Object( ); o.a = 10; // Remove dynamic instance variable a delete o.a; // Remove the element from the XML // object referenced by novel delete novel.TITLE; Table 10-2. ActionScript operators (continued) Operator Precedence Description Example 180 typeof 14 Returns a simple string description of various types of objects. Used for backwards compatibility with ActionScript 1.0 and ActionScript 2.0 only. // Retrieve string description of 35's type typeof 35 void 14 Returns the value undefined var o:Object = new Object( ); o.a = 10; // Compare undefined to the value of o.a if (o.a == void) { trace("o.a does not exist, or has no value"); } * 13 Multiplies two numbers // Calculate four times six 4 * 6 / 13 Divides left operand by right operand // Calculate 30 divided by 5 30 / 5 % 13 Returns the remainder (i.e., modulus ) that results when the left oper- and is divided by the right operand // Calculate remainder of 14 divided by 4 14 % 4 + 12 Multiple uses: • Adds two numbers • Combines(concatenate) two strings • Combines (concatenate) two XML or XMLList objects // Calculate 25 plus 10 25 + 10 // Combine "He" and "llo" to form "Hello" "He" + "llo" // Combine two XML objects <JOB>Programmer</JOB> + <AGE>52</AGE> – 12 Subtracts right operand from left operand // Subtract 2 from 12 12 - 2 << 11 Performs a bitwise left shift // Shift 9 four bits to the left 9 << 4 >> 11 Performs a bitwise signed right shift // Shift 8 one bit to the right 8 >> 1 >>> 11 Performs a bitwise unsigned right shift // Shift 8 one bit to the right, filling // vacated bits with zeros 8 >>> 1 Table 10-2. ActionScript operators (continued) Operator Precedence Description Example 181 < 10 Checks if the left operand is less than the right operand. Depending upon the evaluation of the operands, returns true or false . // Check if 5 is less than 6 5 < 6 // Check if "a" has a lower character code point // than "z" "a" < "z" <= 10 Checks if the left operand is less than or equal to the right operand. Depending on the evaluation of the operands, returns true or false . // Check if 10 is less than or equal to 5 10 <= 5 // Check if "C" has a lower character code point // than "D", or the same code point as "D" "C" <= "D" > 10 Checks if the left operand is greater than the right operand. Depending upon the evaluation of the operands, returns true or false . // Check if 5 is greater than 6 5 > 6 // Check if "a" has a higher character code point // than "z" "a" > "z" >= 10 Checks if the left operand is greater than or equal to the right operand. Depending on the evaluation of the operands, returns true or false . // Check if 10 is greater than or equal to 5 10 >= 5 // Check if "C" has a higher character code point // than "D", or the same code point as "D" "C" >= "D" as Checks if the left operand belongs to the datatype specified by the rightoperand. If yes, returns the object; otherwise returns null var d:Date = new Date( ) // Check if d's value belongs to // the Date datatype d as Date is Checks if the left operand belongs to the datatype specified by the rightoperand. If yes, returns the true ; otherwise returns false . var a:Array = new Array( ) // Check if a's value belongs to // the Array datatype a is Array in Checks if an object has a specified public instance variable or public instance method. Depending on the evaluation of the operands, returnstrue or false . var d:Date = new Date( ) // Check if d's value has a public variable or // public method named getMonth "getMonth" in d Table 10-2. ActionScript operators (continued) Operator Precedence Description Example 182 instanceof 10 Checks if the left operand ’ s prototype chain includes the right operand. Depending on the evaluation of the operands, returns true or false . var s:Sprite = new Sprite( ) // Check if s's value's prototype chain // includes DisplayObject s instanceof DisplayObject == 9 Checks whether two expressions are considered equal (equality). Depending on the evaluation of the operands, returns true or false . // Check whether the expression "hi" is equal to // the expression "hello" "hi" == "hello" != 9 Checks whether two expressions are considered not equal (in equality). Depending upon the evaluation of the operands, returns true or false . // Check whether the expression 3 is not equal to // the expression 3 3 != 3 === 9 Checks whether two expressions are considered equal without datatype conversion for primitive types (strict equality). Depending on the evalu-ation of the operands, returns true or false . // Check whether the expression "3" is equal to // the expression 3. This code compiles in // standard mode only. "3" === 3 !== 9 Checks whether two expressions are considered not equal without datatype conversion for primitive types (strict equality). Depending on the evaluation of the expression, returns true or false. // Check whether the expression "3" is not equal to // the expression 3. This code compiles in // standard mode only. "3" === 3 & 8 Performs a bitwise AND // Combine bits of 15 and 4 using bitwise AND 15 & 4 ^ 7 Performs a bitwise XOR // Combine bits of 15 and 4 using bitwise XOR 15 ^ 4 | 6 Performs a bitwise OR // Combine bits of 15 and 4 using bitwise OR 15 | 4 && 5 Compares two expressions using a logical AND operation. If the left operand is false or converts to false , && returns the left operand; otherwise && returns the right operand. var validUser:Boolean = true; var validPassword:Boolean = false; // Check if both validUser and validPassword // are true if (validUser || validPassword) { // Do login... } Table 10-2. ActionScript operators (continued) Operator Precedence Description Example 183 || 4 Compares two expressions using a logical OR operation. If the left oper- and is true or converts to true , || returns the left operand; otherwise || returns the right operand. var promotionalDay:Boolean = false; var registeredUser:Boolean = false; // Check if either promotionalDay or registeredUser // is true if (promotionalDay || registeredUser) { // Show premium content... } ?: 3 Performs a simple conditional. If the first operand is true or converts to true , the value of the second operand is evaluated and returned. Otherwise, the value of the third operand is evaluated and returned. // Invoke one of two methods based on // whether soundMuted is true soundMuted ? displayVisualAlarm() : playAudioAlarm( ) = 2 Assigns a value to a variable or array element // Assign 36 to variable age var age:int = 36; // Assign a new array to variable seasons var seasons:Array = new Array( ); // Assign "winter" to first element of seasons seasons[0] = "winter"; += 2 Adds (or concatenates) and reassigns // Add 10 to n's value n += 10; // same as n = n + 10; // Add an exclamation mark to the end of msg msg += "!" // Add an <AUTHOR> tag after the first <AUTHOR> // tag child of novel novel.AUTHOR[0] += <AUTHOR>Dave Luxton</AUTHOR>; –= 2 Subtracts and reassigns // Subtract 10 from n's value n -= 10; // same as n = n - 10; *= 2 Multiplies and reassigns // Multiply n's value by 10 n *= 10; // same as n = n * 10; /= 2 Divides and reassigns // Divide n's value by 10 n /= 10; // same as n = n / 10; %= 2 Performs modulo division and reassigns // Assign n%4 to n n %= 4; // same as n = n % 4; Table 10-2. ActionScript operators (continued) Operator Precedence Description Example 184 <<= 2 Shifts bits left and reassigns // Shift n's bits two places to the left n <<= 2; // same as n = n << 2; >>= 2 Shifts bits right and reassigns // Shift n's bits two places to the right n >>= 2; // same as n = n >> 2; >>>= 2 Shifts bits right (unsigned) and reassigns // Shift n's bits two places to the right, filling // vacated bits with zeros n >>>= 2; // same as n = n >>> 2; &= 2 Performs bitwise AND and reassigns // Combine n's bits with 4 using bitwise AND n &= 4 // same as n = n & 4; ^= 2 Performs bitwise XOR and reassigns // Combine n's bits with 4 using bitwise XOR n ^= 4 // same as n = n ^ 4; |= 2 Performs bitwise OR and reassigns // Combine n's bits with 4 using bitwise OR n |= 4 // same as n = n | 4; , 1 Evaluates left operand, then right operand // Initialize and increment two loop counters for (var i:int = 0, j:int = 10; i < 5; i++, j++) { // i counts from 0 through 4 // j counts from 10 through 14 } Table 10-2. ActionScript operators (continued) Operator Precedence Description Example Up Next: Managing Lists of Information | 185 Up Next: Managing Lists of Information This chapter covered some of ActionScript’s basic built-in programming tools. In the next chapter, we’ll study another essential ActionScript tool: arrays. Arrays are used to manage lists of information. 186 Chapter 11CHAPTER 11 Arrays 12 Arrays store and manipulate ordered lists of information and are, therefore, a funda- mental tool in sequential, repetitive programming. We use arrays to do everything from storing user input, to generating pull-down menus, to keeping track of enemy spacecraft in a game. Practically speaking, an array is just a list of items, like your grocery list or the entries in your checkbook ledger. The items just happen to be ActionScript values. What Is an Array? An array is a data structure that can encompass multiple individual data values in an ordered list. Here is a simple example showing two separate strings, followed by an array that contains two strings: "cherries" // A single string "peaches" // Another string ["oranges", "apples"] // A single array containing two strings An array can contain any number of items, including items of different types. An array can even contain other arrays. Here is a simple example showing an array that contains both strings and numbers. It might represent your shopping list, showing how many of each item you intend to buy: ["oranges", 6, "apples", 4, "bananas", 3]; Though an array can keep track of many values, it’s important to recognize that the array itself is a single data value. Arrays are represented as instances of the Array class. As such, an array can be assigned to a variable or used as part of a complex expression: // Assign an array to a variable var product:Array = ["ladies downhill skis", 475]; // Pass that array to a function display(product); Creating Arrays | 187 The Anatomy of an Array Each item in an array is called an array element, and each element has a unique numeric position (index) by which we can refer to it. Array Elements Like a variable, each array element can be assigned any value. An entire array, then, is akin to a collection of sequentially named variables, but instead of each item hav- ing a different name, each item has an element number (the first element is number 0, not number 1). To manipulate the values in an array’s elements, we ask for them by number. Array Element Indexing An element’s position in the array is known as its index. We use an element’s index to set or retrieve the element’s value or to work with the element in various ways. Some of the array-handling methods, for example, use element indexes to specify ranges of elements for processing. We can also insert and delete elements from the beginning, end, or even middle of an array. An array can have gaps (that is, some elements can be empty). We can have elements at positions 0 and 4, without requiring anything in positions 1, 2, and 3. Arrays with gaps are called sparse arrays. Array Size At any point during its life span, a given array has a specific number of elements (both empty and occupied). The number of elements in an array is called the array’s length, which we’ll discuss later in this chapter. Creating Arrays To create a new array, we use an array literal or the new operator (i.e., new Array( )). Creating Arrays with Array Literals In an array literal, square brackets demarcate the beginning and end of the array. Inside the square brackets, the values of the array’s elements are specified as a comma-separated list. Here’s the general syntax: [expression1, expression2, expression3] 188 | Chapter 11: Arrays The expressions are resolved and then assigned to the elements of the array being defined. Any valid expression can be used, including function calls, variables, liter- als, and even other arrays (an array within an array is called a nested array or a two- dimensional array). Here are a few examples: // Simple numeric elements [4, 5, 63]; // Simple string elements ["apple", "orange", "pear"] // Numeric expressions with an operation [1, 4, 6 + 10] // Variable values and strings as elements [firstName, lastName, "tall", "skinny"] // A nested array literal ["month end days", [31, 30, 28]] Arrays in Other Programming Languages Almost every high-level computer language supports arrays or array-like entities. That said, there are differences in the ways arrays are implemented across different lan- guages. For example, many languages do not allow arrays to contain differing types of data. In many languages, an array can contain numbers or strings, but not both in the same array. Interestingly, in C, there is no primitive string datatype. Instead, C has a single-character datatype named char; strings are considered a complexdatatype and are implemented as an array of chars. In ActionScript, the size of an array changes automatically as items are added or removed. In many languages, the size of an array must be specified when the array is first declared or dimensioned (i.e., when memory is allocated to hold the array’s data). Languages differ as to what happens when a program attempts to access an element whose indexis outside the bounds (limits) of the array. ActionScript adds elements if a program attempts to set a value for an element beyond the existing bounds of the array. If a program attempts to access an element by an indexoutside the array bounds, ActionScript returns undefined, whereas C, for example, pays no attention to whether the element number is valid. It lets the program retrieve and set elements outside the bounds of the array, which usually results in the access of meaningless data that is not part of the array, or causes other data in memory to be overwritten (C gives you plenty of rope with which to hang yourself). Referencing Array Elements | 189 Creating Arrays with the new Operator To create an array with the new operator, we use the following generalized code: new Array(arguments) The result of the preceding code depends on the number and type of arguments sup- plied to the Array constructor. When more than one argument is supplied, or when a single nonnumeric argument is supplied, each argument becomes one of the element values in the new array. For example, the following code creates an array with three elements: new Array("sun", "moon", "earth") When exactly one numeric argument is supplied to the Array( ) constructor, it cre- ates an array with the specified number of empty placeholder elements (creating such an array with an array literal is cumbersome). For example, the following code cre- ates an array with 14 empty elements: new Array(14) Arguments passed to the Array( ) constructor can be any legal expression, including compound expressions. For example, the following code creates an array whose first element is an 11 and second element is 50: var x:int = 10; var y:int = 5; var numbers:Array = new Array(x + 1, x * y); For direct comparison, the following code creates the arrays from the previous sec- tion, but using the new operator instead of array literals: new Array(4, 5, 63) new Array("apple", "orange", "pear") new Array(1, 4, 6 + 10) new Array(firstName, lastName, "tall", "skinny") new Array("month end days", new Array(31, 30, 28)) Referencing Array Elements Once we’ve created an array, we’ll inevitably want to retrieve or change the value of its elements. To do so, we use the array access operator, []. Retrieving an Element’s Value To access an individual element, we provide a reference to the array followed by the element’s index within square brackets, as follows: theArray[elementNumber] In the preceding code, theArray is a reference to the array (usually a variable with an array as a value), and elementNumber is an integer specifying the element’s index. The 190 | Chapter 11: Arrays first element is number 0, and the last element number is 1 less than the array’s length. Specifying an element number greater than the last valid element number causes ActionScript to return undefined (because the specified indexis outside the bounds of the array). Let’s try retrieving some element values. The following code creates an array using an array literal, and assigns it to the variable trees: var trees:Array = ["birch", "maple", "oak", "cedar"]; The following code assigns the value of the first element of trees (“birch”) to a vari- able, firstTree: var firstTree:String = trees[0]; The following code assigns the third element’s value (“oak”) to the variable favoriteTree (remember that indexes start at 0, so index 2 is the third element!) var favoriteTree:String = trees[2]; Now here’s the fun part. Because we can specify the indexof an element as any number-yielding expression, we can use variables or complex expressions just as eas- ily as we use numbers to specify an element index. For example, the following code assigns the fourth element’s value (“cedar”) to the variable lastTree: var i = 3; var lastTree:String = trees[i]; We can even use call expressions that have numeric return values as array indexes. For example, the following code sets randomTree to a randomly chosen element of trees by calculating a random number between 0 and 3: var randomTree:String = trees[Math.floor(Math.random( ) * 4)]; Nice. You might use a similar approach to pick a random question from an array of trivia questions or to pick a random card from an array that represents a deck of cards. Note that accessing an array element is very similar to accessing a variable value. Array elements can be used as part of any complex expression, as follows: var ages:Array = [12, 4, 90]; var totalAge:Number = ages[0] + ages[1] + ages[2]; // Sum the array Summing the values of an array’s elements manually isn’t exactly the paragon of optimized code. Later, we’ll see a much more convenient way to access an array’s elements sequentially. Setting an Element’s Value To set an element’s value, we use arrayName[elementNumber] as the left-side operand of an assignment expression. The following code demonstrates: // Make an array var cities:Array = ["Toronto", "Montreal", "Vancouver", "Waterloo"]; // cities is now: ["Toronto", "Montreal", "Vancouver", "Waterloo"] Determining the Size of an Array | 191 // Set the value of the array's first element cities[0] = "London"; // cities becomes ["London", "Montreal", "Vancouver", "Waterloo"] // Set the value of the array's fourth element cities[3] = "Hamburg"; // cities becomes ["London", "Montreal", "Vancouver", "Hamburg"] // Set the value of the array's third element cities[2] = 293.3; // Notice that the datatype change is not a problem // cities becomes ["London", "Montreal", 293.3, "Hamburg"] Note that we can use any nonnegative numeric expression as the index when setting an array element: var i:int = 1; // Set the value of element i cities[i] = "Tokyo"; // cities becomes ["London", "Tokyo", 293.3, "Hamburg"] Determining the Size of an Array All arrays come with an instance variable named length, which indicates the current number of elements in the array (including undefined elements). To access an array’s length variable, we use the dot operator, like so: theArray.length Here are a few examples: var list:Array = [34, 45, 57]; trace(list.length); // Displays: 3 var words:Array = ["this", "that", "the other"]; trace(words.length); // Displays: 3 var cards:Array = new Array(24); // Note the single numeric argument // used with the Array( ) constructor trace(cards.length); // Displays: 24 The length of an array is always 1 greater than the indexof its last element. For example, an array with elements at indexes 0, 1, and 2 has a length of 3. And an array with elements at indexes 0, 1, 2, and 50 has a length of 51. 51? Yes, 51. Even though indexes 3 through 49 are empty, they still contribute to the length of the array. The indexof the last element of an array is always theArray.length – 1 (because indexnumbers begin at 0, not 1). Therefore, to access the last element of theArray, we use the following code: theArray[theArray.length – 1] If we add and remove elements, the array’s length variable is updated to reflect our changes. In fact, we can even set the length variable to add or remove elements at the end of an array. This is in contrast to the String class’s length variable, which is read- only. Shortening the length of an array removes elements beyond the new length. 192 | Chapter 11: Arrays Using an array’s length variable, we can create a loop that accesses all the elements of an array. Looping through an array’s elements is a fundamental task in program- ming. To get a sense of what’s possible when we combine loops and arrays, study Example 11-1, which hunts through a soundtracks array to find the location of the element with the value “hip hop.” Let’s extend Example 11-1 into a generalized search method that can check any array for any matching element. The method will return the position within the array where the element was found or -1 if it was not found. Example 11-2 shows the code. Here’s how to use our new search method to check whether or not “Dan” is one of the names in our userNames array, which is a hypothetical array of authorized user- names: if (searchArray(userNames, "Dan") == -1) { trace("Sorry, that username wasn't found"); } else { trace("Welcome to the game, Dan."); } The searchArray( ) method demonstrates the code required to loop through an array’s elements but is not intended for use in a real pro- gram. To search for a given element’s indexin a real program, you should use the Array class’s indexOf( ) and lastIndexOf( ) methods. Example 11-1. Searching an array // Create an array var soundtracks:Array = ["electronic", "hip hop", "pop", "alternative", "classical"]; // Check each element to see if it contains "hip hop" for (var i:int = 0; i < soundtracks.length; i++) { trace("Now examining element: " + i); if (soundtracks[i] == "hip hop") { trace("The location of 'hip hop' is index: " + i); break; } } Example 11-2. A generalized array-searching function public function searchArray (theArray:Array, searchElement:Object):int { // Check each element to see if it contains searchElement for (var i:int = 0; i < theArray.length; i++) { if (theArray[i] == searchElement) { return i; } } return -1; } Adding Elements to an Array | 193 The remainder of this chapter explains more about the mechanics of manipulating arrays, including the use of Array methods. Adding Elements to an Array To add elements to an array, we use one of the following techniques: • Specify a value for a new element at an indexequal to or greater than the array’s length • Increase the array’s length variable • Invoke push( ), unshift( ), splice( ) or concat( ) on the array The following sections discuss these techniques in detail. Adding New Elements Directly To add a new element to an existing array at a specific index, we simply assign a value to that element. The following code demonstrates: // Create an array, and assign it three values var fruits:Array = ["apples", "oranges", "pears"]; // Add a fourth value fruits[3] = "tangerines"; The new element does not need to be placed immediately after the last element of the array. If we place the new element more than one element beyond the end of the array, ActionScript automatically creates undefined elements for the intervening indexes: // Leave indexes 4 to 38 empty fruits[39] = "grapes"; trace(fruits[12]); // Displays: undefined If the element already exists, it will be replaced by the new value. If the element doesn’t exist, it will be added. Adding New Elements with the length Variable To extend an array without assigning values to new elements, we can simply increase the length variable, and ActionScript will add enough elements to reach that length: // Create an array with three elements var colors = ["green", "red", "blue"]; // Add 47 empty elements, numbered 3 through 49, to the array colors.length = 50; You can use this approach to create a number of empty elements to hold some data you expect to accumulate, such as student test scores. Even though the elements are 194 | Chapter 11: Arrays empty, they can still be used to indicate that an expected value has not yet been assigned. For example, a loop that displays test scores on screen could generate default output, “No Score Available,” for empty elements. Adding New Elements with Array Methods We can use Array methods to handle more complex element-addition operations. The push( ) method The push( ) method appends one or more elements to the end of an array. It automat- ically appends the data after the last numbered element of the array, so there’s no need to worry about how many elements already exist. The push( ) method can also append multiple elements to an array at once. The push( ) method has the following general form: theArray.push(item1, item2,...itemn); In the preceding code, theArray is a reference to an Array object, and item1, item2, ...itemn is a comma-separated list of items to be appended to the end of the array as new elements. Here are some examples: // Create an array with two elements var menuItems:Array = ["home", "quit"]; // Add an element menuItems.push("products"); // menuItems becomes ["home", "quit", "products"] // Add two more elements menuItems.push("services", "contact"); // menuItems becomes ["home", "quit", "products", "services", "contact"] The push( ) method returns the new length of the updated array (i.e., the value of the length variable): var list:Array = [12, 23, 98]; trace(myList.push(28, 36)); // Appends 28 and 36 to list and displays: 5 Note that the items added to the list can be any expression. The expression is resolved before being added to the list: var temperature:int = 22; var sky:String = "sunny"; var weatherListing:Array = new Array( ); // Add 22 and "sunny" to the array weatherListing.push(temperature, sky); The unshift( ) method The unshift( )method is much like push( ), but it adds one or more elements to the beginning of the array, bumping all existing elements up to make room (i.e., the Adding Elements to an Array | 195 indexes of existing elements increase to accommodate the new elements at the begin- ning of the array). The unshift( ) method has the following general form: theArray.unshift(item1, item2,...itemn); In the preceding code, theArray is a reference to an Array object, and item1, item2, ...itemn is a comma-separated list of items to be added to the beginning of the array as new elements. Note that multiple items are added in the order that they are supplied. Here are some examples: var versions:Array = new Array( ); versions[0] = 6; versions.unshift(5); // versions is now [5, 6] versions.unshift(2,3,4); // versions is now [2, 3, 4, 5, 6] The unshift( ) method, like push( ), returns the length of the newly enlarged array. Pushing, Popping, and Stacks The push( ) method takes its name from a programming concept called a stack. A stack can be thought of as a vertical array, like a stack of dishes. If you frequent cafeterias or restaurants with buffets, you should be familiar with the spring-loaded racks that hold plates for the customers. When clean dishes are added, they are literally pushed onto the top of the stack, and the older dishes sink lower into the rack. When a customer pops a dish from the top of the stack, she is removing the dish that was most recently pushed onto the stack. This is known as a last-in-first-out (LIFO) stack and is typically used for things like history lists. For example, if you hit the Back button in your browser, it will take you to the previous web page you visited. If you hit the Back but- ton again, you’ll be brought to the page before that, and so on. This is achieved by pushing the URL of each page you visit onto the stack and popping it off when the Back button is clicked. LIFO stacks can also be found in real life. The last person to check her luggage on an airplane usually receives her luggage first when the plane lands, because the luggage is unloaded in the reverse order from which it was loaded. The early bird who checked his luggage first must wait the longest at the luggage conveyor belt after the plane lands. A first-in-first-out (FIFO) stack is more egalitarian; it works on a first-come-first- served basis. A FIFO stack is like the line at your local bank. Instead of taking the last element in an array, a FIFO stack deals with the first element in an array next. It then deletes the first element in the array, and all the other elements “move up,” just as you move up in line when the person in front of you is “deleted” (i.e., either she is served and then leaves, or she chooses to leave in disgust because she is tired of waiting). Therefore, the word push generally implies that you are using a LIFO stack, whereas the word append implies that you are using a FIFO stack. In either case, elements are added to the “end” of the stack; the difference lies in which end of the array holds the element that is taken for the next operation. 196 | Chapter 11: Arrays The splice( ) method The splice( ) method can add elements to, or remove elements from, an array. It is typically used to insert elements into the middle of an array (later elements are renumbered to make room) or to delete elements from the middle of an array (later elements are renumbered to close the gap). When splice( ) performs both tasks in a single invocation, it effectively replaces some elements with new elements (though not necessarily with the same number of elements). The splice( ) method has the fol- lowing general form: theArray.splice(startIndex, deleteCount, item1, item2,...itemn) In the preceding code, theArray is a reference to an Array object; startIndex is a number that specifies the indexat which element removal and optional insertion should commence (remember that the first element’s indexis 0); deleteCount is an optional argument that dictates how many elements should be removed (including the element at startIndex). When deleteCount is omitted, every element after and including startIndex is removed. The optional item1, item2, ...itemn parameters are items to be added to the array as elements starting at startIndex. Example 11-3 shows the versatility of the splice( ) method. Example 11-3. Using the splice( ) array method // Make an array... var months:Array = new Array("January", "Friday", "April", "May", "Sunday", "Monday", "July"); // Hmmm. Something's wrong with our array. Let's fix it up. // First, let's get rid of "Friday". months.splice(1,1); // months is now: // ["January", "April", "May", "Sunday", "Monday", "July"] // Now, let's add the two months before "April". // Note that we won't delete anything here (deleteCount is 0). months.splice(1, 0, "February", "March"); // months is now: // ["January", "February", "March", "April", // "May", "Sunday", "Monday", "July"] // Finally, let's remove "Sunday" and "Monday" while inserting "June". months.splice(5, 2, "June"); // months is now: // ["January", "February", "March", "April", "May", "June", "July"] // Now that our months array is fixed, let's trim it // so that it contains only the first quarter of the year, // by deleting all elements starting with index 3 (i.e., "April"). months.splice(3); // months is now: ["January", "February", "March"] Removing Elements from an Array | 197 The splice( ) method returns an array of the elements it removes. Thus it can be used to extract a series of elements from an array: var letters:Array = ["a", "b", "c", "d"]; trace(letters.splice(1, 2)); // Displays: "b,c" // letters is now ["a", "d"] If no elements are removed, splice( ) returns an empty array (that is, an array with no elements). The concat( ) method The concat( ) method combines two or more arrays into a single, new array, which it returns. The concat( ) method has the following general form: origArray.concat(elementList) The concat( ) method appends the elements contained in elementList, one by one, to the end of origArray and returns the result as a new array, leaving origArray untouched. Normally, we store the returned array in a variable. Here, simple num- bers are used as the items to be added to the array: var list1:Array = new Array(11, 12, 13); var list2:Array = list1.concat(14, 15); // list2 becomes // [11, 12, 13, 14, 15] In the following example, we use concat( ) to combine two arrays: var guests:Array = ["Panda", "Dave"]; var registeredPlayers:Array = ["Gray", "Doomtrooper", "TRK9"]; var allUsers:Array = registeredPlayers.concat(guests); // allUsers is now: ["Gray", "Doomtrooper", "TRK9", "Panda", "Dave"] Notice that concat( ) broke apart, or “flattened” the guests array when adding it to allUsers; that is, each element of the guests array was added to allUsers individu- ally. However, concat( ) does not flatten nested arrays (elements that are themselves arrays within the main array), as you can see from the following code: var x:Array = [1, 2, 3]; var y:Array = [[5, 6], [7, 8]]; var z:Array = x.concat(y); // Result is [1, 2, 3, [5, 6], [7, 8]]. // Elements 0 and 1 of y were not "flattened" Removing Elements from an Array To remove elements from an array, we use one of the following techniques: • Delete the specific element with the delete operator • Decrease the array’s length variable • Invoke push( ), unshift( ),or splice( ) on the array The following sections discuss these techniques in detail. 198 | Chapter 11: Arrays Removing Elements with the delete Operator The delete operator sets an array element to undefined, using the following syntax: delete theArray[index] In the preceding code, theArray is a reference to an array, and index is the number or name of the element whose value should be set to undefined. The name delete is, frankly, misleading. It does not remove the numbered element from the array; it merely sets the target element’s value to undefined.Adelete operation, therefore, is identical to assigning the undefined value to an element. We can verify this by check- ing the length variable of an array after deleting one of its elements: var list = ["a", "b", "c"]; trace(list.length); // Displays: 3 delete list[2]; trace(list.length); // Still displays 3...the element at index 2 is // undefined instead of "c", but it still exists To truly delete elements, use splice( ) (to delete them from the middle of an array), or use shift( ) and pop( ) (to delete them from the beginning or end of an array). Removing Elements with the length Variable To delete elements from the end of the array (i.e., truncate the array), we can set the array’s length variable to a number smaller than the current length: var toppings:Array = ["pepperoni", "tomatoes", "cheese", "green pepper", "broccoli"]; toppings.length = 3; trace(toppings); // Displays: "pepperoni,tomatoes,cheese" // We trimmed elements 3 and 4 (the last two) Removing Elements with Array Methods Arrays come equipped with several built-in methods for removing elements. We’ve already seen how splice( ) can delete a series of elements from the middle of an array. The pop( ) and shift( ) methods are used to prune elements from the end or begin- ning of an array. The pop( ) method The pop( ) method is the antithesis of push( ): it removes the last element of an array. The syntax of pop( ) is simple: theArray.pop( ) I don’t know why, but I always think that “popping” an array is kinda funny. Any- way, pop( ) decrements the array’s length by 1 and returns the value of the element it removed. For example: Checking the Contents of an Array with the toString( ) Method | 199 var numbers:Array = [56, 57, 58]; trace(numbers.pop( )); // Displays: 58 (the value of the popped element) // numbers is now [56, 57] As we saw earlier, pop( ) is often used in combination with push( ) to perform LIFO stack operations. The shift( ) method Remember unshift( ), the method we used to add an element to the beginning of an array? Meet its alter ego, shift( ), which removes an element from the beginning of an array: theArray.shift( ) Not as funny as pop( ). Oh well. Like pop( ), shift( ) returns the value of the element it removes. The remaining elements all move up in the pecking order toward the beginning of the array. For example: var sports:Array = ["quake", "snowboarding", "inline skating"]; trace(sports.shift( )); // Displays: quake // sports is now ["snowboarding", "inline skating"] trace(sports.shift( )); // Displays: snowboarding // sports is now ["inline skating"] Because shift( ) truly deletes an element, it is more useful than delete for removing the first element of an array. The splice( ) method Earlier we saw that splice( ) can both remove elements from and add elements to an array. Because we’ve already looked at splice( ) in detail, we won’t reexamine it here. However, for reference, the following code specifically demonstrates splice( )’s element-removal capabilities: var letters:Array = ["a", "b", "c", "d", "e", "f"]; // Remove elements 1, 2, and 3, leaving ["a", "e", "f"] letters.splice(1, 3); // Remove elements 1 through the end leaving just ["a"] letters.splice(1); Checking the Contents of an Array with the toString( ) Method The toString( ) method, common to all objects, returns a string representation of the object upon which it is invoked. In the case of an Array object, the toString( ) method 200 | Chapter 11: Arrays returns a list of the array’s elements, converted to strings and separated by commas. The toString( ) method can be called explicitly, as follows: theArray.toString( ) Typically, however, toString( ) isn’t used explicitly; rather, it is invoked automati- cally whenever theArray is used in a string context. For example, the expression trace(theArray) outputs a list of comma-separated element values during debug- ging; trace(theArray) is equivalent to trace(theArray.toString( )). The toString( ) method is often helpful during debugging when we need a quick, unformatted look at the elements of an array. For example: var sites = ["www.moock.org", "www.adobe.com", "www.oreilly.com"]; trace("The sites array is " + sites); Note that the join( ) method offers greater formatting flexibility than toString( ). For details, see Adobe’s ActionScript Language Reference. Multidimensional Arrays So far, we’ve limited our discussion to one-dimensional arrays, which are akin to a single row or a single column in a spreadsheet. But what if we want to create the equivalent of a spreadsheet with both rows and columns? We need a second dimen- sion. ActionScript natively supports only one-dimensional arrays, but we can simu- late a multidimensional array by creating arrays within arrays. That is, we can create an array that contains elements that are themselves arrays (sometimes called nested arrays). The simplest type of multidimensional array is a two-dimensional array, in which elements are organized conceptually into a grid of rows and columns; the rows are the first dimension of the array, and the columns are the second. Using a practical example, let’s consider how a two-dimensional array works. Sup- pose we’re processing an order that contains three products, each with a quantity and a price. We want to simulate a spreadsheet with three rows (one for each prod- uct) and two columns (one for the quantity and one for the price). We create a sepa- rate array for each row, with each row’s elements representing the values in each column: var row1:Array = [6, 2.99]; // Quantity 6, Price 2.99 var row2:Array = [4, 9.99]; // Quantity 4, Price 9.99 var row3:Array = [1, 59.99]; // Quantity 1, Price 59.99 Next, we place the rows into a container array named spreadsheet: var spreadsheet:Array = [row1, row2, row3]; Now we can find the total cost of the order by multiplying the quantity and price of each row and adding them all together. We access a two-dimensional array’s ele- ments using two indexes (one for the row and one for the column). The expression On to Events | 201 spreadsheet[0], for example, represents the first row’s two-column array. Hence, to access the second column in the first row of spreadsheet,weusespreadsheet[0][1] (which yields 2.99). Here’s how to calculate the total price of the items in spreadsheet: // Create a variable to store the total cost of the order. var total:Number; // Now find the cost of the order. For each row, multiply the columns // together, and add that to the total. for (var i:int = 0; i < spreadsheet.length; i++) { total += spreadsheet[i][0] * spreadsheet[i][1]; } trace(total); // Displays: 117.89 On to Events This chapter offered an introduction to arrays but is by no means exhaustive. The Array class offers many useful methods for reordering and sorting array elements, fil- tering elements, converting elements to strings, and extracting arrays from other arrays. For details, see the Array class in Adobe’s ActionScript Language Reference. Our next topic of study is event handling—a built-in system for managing communi- cation between objects. 202 Chapter 12CHAPTER 12 Events and Event Handling 13 In general terms, an event is a noteworthy runtime occurrence that has the potential to trigger a response in a program. In ActionScript, events can be broken into two categories: built-in events, which describe changes to the state of the runtime envi- ronment, and custom events, which describe changes to the state of a program. For example, a built-in event might be the clicking of the mouse or the completion of a file-load operation. By contrast, a custom event might be the ending of a game or the submission of an answer in a quiz. Events are ubiquitous in ActionScript. In fact, in a pure ActionScript program, once the main-class constructor method has finished executing, all subsequent code is triggered by events. Accordingly, ActionScript supports a rich event architecture that provides the foundation for both built-in and custom events. ActionScript’s event architecture is based on the W3C Document Object Model (DOM) Level 3 Events Specification, available at http:// www.w3.org/TR/DOM-Level-3-Events. This chapter teaches the fundamentals of ActionScript’s event architecture, covering both how to respond to built-in events and how to implement custom events in an ActionScript program. Note, however, that this chapter covers event fundamentals only. Later, in Chapter 21, we’ll study how ActionScript’s event architecture caters to display objects (objects that represent onscreen content). Then, in Chapter 22, we’ll examine a variety of specific built-in user-input events. ActionScript Event Basics In order to handle (respond to) events in an ActionScript program, we use event lis- teners. An event listener is a function or method that registers to be executed when a given event occurs. Event listeners are so named because they conceptually wait (listen) for events to happen. To notify a program that a given event has occurred, ActionScript Event Basics | 203 ActionScript executes any and all event listeners that have registered for that event. The notification process is known as an event dispatch. When a given event dispatch is about to begin, ActionScript creates an object— known as the event object—that represents the event. The event object is always an instance of the Event class or one of its descendants. All event listeners executed dur- ing the event dispatch are passed a reference to the event object as an argument. Each listener can use the event object’s variables to access information relating to the event. For example, a listener for an event representing mouse activity might use the variables of the event object to determine the location of the mouse pointer at the time of the event. Every type of event in ActionScript—whether built-in or custom—is given a string name. For example, the name of the “mouse click” event type is “click.” During an event dispatch, the name of the event being dispatched can be retrieved via the type variable of the event object passed to every listener. Each event dispatch in ActionScript has an event target, which is the object to which the event conceptually pertains. For example, for input events, the event target is typically the object that was manipulated (clicked on, typed into, moved over, etc.). Likewise, for network events, the event target is typically the object that instigated the network operation. To respond to a given event, listeners typically register with the event target. Accord- ingly, all event target objects are instances of a class that inherits from the EventDispatcher class or that implements the IEventDispatcher interface. The EventDispatcher class provides methods for registering and unregistering event listen- ers (addEventListener( ) and removeEventListener( ), respectively). In Chapter 21, we’ll learn that when the event target is a display object (an object that can be displayed on screen), event listeners can also register with the event target’s display ancestors (i.e., objects that visually contain the event target). For now, how- ever, we’ll concentrate solely on nondisplayable event-target objects. Registering an Event Listener for an Event The general process for responding to an event in ActionScript is as follows: 1. Determine the name of the event’s event type. 2. Determine the datatype of the event object representing the event. 3. Create an event listener to respond to the event. The event listener must define a single parameter matching the datatype of the event object from Step 2. 4. Use EventDispatcher class’s instance method addEventListener( ) to register the event listener with the event target (or, any display ancestor of the event target). 5. Sit back and wait for the event to occur. 204 | Chapter 12: Events and Event Handling Let’s apply the preceding steps to an example: registering for the built-in “complete” event. Step 1: Determine the event type’s name Flash client runtimes offer a wide range of built-in event types, representing every- thing from user input to network and sound activity. Each event type’s name is accessible via a constant of the Event class or one of its descendants. For example, the constant for the “operation complete” event type is Event.COMPLETE, whose value is the string name “complete.” Likewise, the constant for the “mouse pressed” event type is MouseEvent.MOUSE_DOWN, whose value is the string name “mouseDown.” In order to respond to a given built-in event type, we must first find the constant that represents it. In Adobe’s ActionScript Language Reference, event constants are listed under the Events heading for any class that supports events (i.e., inherits from EventDispatcher). Hence, to find the constant for a given built-in event, we check the Events heading in the documentation for the class to which the event pertains. For example, suppose we’re loading an external text file using the URLLoader class, and we want to execute some code when the file finishes loading. We check the Events heading of the URLLoader class to see if the appropriate “done loading” event is available. Under the Events heading we find an entry for the “complete” event that seems to suit our purpose. Here’s what the “complete” event entry looks like: complete event Event object type: flash.events.Event Event.type property = flash.events.Event.COMPLETE Dispatched after all the received data is decoded and placed in the data property of the URLLoader object. The received data may be accessed once this event has been dis- patched. The “Event.type property” tells us the constant for the “complete” event—flash. events.Event.COMPLETE. We’ll use that constant when registering for the “complete” event, as shown in bold in the following generic code: theURLLoader.addEventListener(Event.COMPLETE, someListener); From now on, when referring to any built-in event, we’ll use the event constant (e.g., Event.COMPLETE) rather than the string-literal name (e.g., “complete”). While slightly verbose, this style promotes developer familiarity with the event constants actually used in ActionScript programs. Step 2: Determine the event object’s datatype Now that we’ve determined our event type’s name (Event.COMPLETE), we must deter- mine the datatype of its event object. Once again, we use the “complete” event entry under the URLLoader class in Adobe’s ActionScript Language Reference. The “Event ActionScript Event Basics | 205 object type” subheading of the “complete” entry (shown in the previous section) tells us the datatype of Event.COMPLETE’s Event object—flash.events.Event. Step 3: Create the event listener Now that we know the constant and event object datatype for our event (Event.COMPLETE and Event, respectively), we can create our event listener. Here’s the code: private function completeListener (e:Event):void { trace("Load complete"); } Notice that our listener defines a parameter (e) that will receive the event object at event- dispatch time. The parameter’s datatype matches the datatype for the Event.COMPLETE event, as determined in Step 2. By convention, all event listeners have a return type of void. Furthermore, event lis- teners that are methods are typically declared private so that they cannot be invoked by code outside of the class in which they are defined. While there is no standard for naming event listener functions or methods, event lis- teners in this book are named using the format eventNameListener, where eventName is the string name of the event (in our example, “complete”). Step 4: Register for the event With our event listener now defined, we’re ready to register for the event. Recall that we’re loading an external text file using an instance of the URLLoader class. That instance will be our event target (because it initiates the load operation that eventu- ally results in the Event.COMPLETE event). The following code creates the URLLoader instance: var urlLoader:URLLoader = new URLLoader( ); And the following code registers our listener, completeListener( ), with our event tar- get, urlLoader, for Event.COMPLETE events: urlLoader.addEventListener(Event.COMPLETE, completeListener); The first argument to addEventListener( ) specifies the name of the event type for which we are registering. The second argument to addEventListener( ) provides a ref- erence to the listener being registered. Here’s the complete method signature for addEventListener( ): addEventListener(type, listener, useCapture, priority, useWeakReference) The first two parameters (type and listener) are required; the remaining parameters are optional. We’ll study priority and useWeakReference later in this chapter, and we’ll study useCapture in Chapter 21. 206 | Chapter 12: Events and Event Handling Step 5: Wait for the event to occur We’ve now created an event listener for the Event.COMPLETE event, and registered it with the event target. To make the Event.COMPLETE event occur, in turn causing the execution of completeListener( ), we initiate a file-load operation, as follows: urlLoader.load(new URLRequest("someFile.txt")); When someFile.txt finishes loading, ActionScript dispatches an Event.COMPLETE event targeted at urlLoader, and completeListener( ) executes. Example 12-1 shows the code for the preceding five steps in the context of a func- tional class, FileLoader. For practice, let’s now register two more events. Two More Event Listener Registration Examples When the code in Example 12-1 runs, if the Flash client runtime cannot find someFile.txt, it dispatches an IOErrorEvent.IO_ERROR event targeted at urlLoader. Let’s register for that event so that our application can handle load failures grace- fully. We’ll start by creating a new event listener, ioErrorListener( ), as follows: private function ioErrorListener (e:Event):void { trace("Error loading file."); } Next, we register ioErrorListener( ) with urlLoader for IOErrorEvent.IO_ERROR events: urlLoader.addEventListener(IOErrorEvent.IO_ERROR, ioErrorListener); Example 12-1. Registering for Event.COMPLETE events package { import flash.display.*; import flash.net.*; import flash.events.*; public class FileLoader extends Sprite { public function FileLoader ( ) { // Create the event target var urlLoader:URLLoader = new URLLoader( ); // Register the event listener urlLoader.addEventListener(Event.COMPLETE, completeListener); // Start the operation that will trigger the event urlLoader.load(new URLRequest("someFile.txt")); } // Define the event listener private function completeListener (e:Event):void { trace("Load complete"); } } } ActionScript Event Basics | 207 Nice and simple. Example 12-2 shows our new IOErrorEvent.IO_ERROR code in the context of the FileLoader class. Now let’s try responding to a completely different built-in Flash client runtime event, Event.RESIZE. The Event.RESIZE event is dispatched whenever a Flash runtime is in “no-scale” mode, and the application window changes width or height. The event target for Event.RESIZE events is the Flash client runtime’s Stage instance. We’ll access that instance through the stage variable of our application’s main class, ResizeMonitor. (If you’re not familiar with the Stage instance, for now simply think of it as representing the Flash client runtime’s display area. We’ll study the Stage class in more detail in Chapter 20.) Here’s the code: package { import flash.display.*; import flash.net.*; import flash.events.*; public class ResizeMonitor extends Sprite { public function ResizeMonitor ( ) { // Use "no-scale" mode. (Otherwise, the content // scales automatically when the application window is resized, and // no Event.RESIZE events are dispatched.) stage.scaleMode = StageScaleMode.NO_SCALE; Example 12-2. Registering for IOErrorEvent.IO_ERROR events package { import flash.display.*; import flash.net.*; import flash.events.*; public class FileLoader extends Sprite { public function FileLoader ( ) { var urlLoader:URLLoader = new URLLoader( ); urlLoader.addEventListener(Event.COMPLETE, completeListener); urlLoader.addEventListener(IOErrorEvent.IO_ERROR, ioErrorListener); urlLoader.load(new URLRequest("someFile.txt")); } private function completeListener (e:Event):void { trace("Load complete"); } private function ioErrorListener (e:Event):void { trace("Error loading file."); } } } 208 | Chapter 12: Events and Event Handling // Register resizeListener( ) with the Stage instance for // Event.RESIZE events. stage.addEventListener(Event.RESIZE, resizeListener); } // Define the event listener, executed whenever the Flash runtime // dispatches the Event.RESIZE event private function resizeListener (e:Event):void { trace("The application window changed size!"); // Output the new Stage dimensions to the debugging console trace("New width: " + stage.stageWidth); trace("New height: " + stage.stageHeight); } } } Notice that within the resizeListener( ) function, stage is directly accessible, just as it is within the ResizeMonitor constructor method. When an event listener is an instance method, it retains full access to the methods and variables of its instance. See “Bound Methods” in Chapter 3. Unregistering an Event Listener for an Event To stop an event listener from receiving event notifications, we unregister it using the EventDispatcher class’s instance method removeEventListener( ), which has the fol- lowing general form: eventTargetOrTargetAncestor.removeEventListener(type, listener, useCapture) In most cases, only the first two parameters (type and listener) are required; we’ll study useCapture in Chapter 21. To reduce memory and processor usage, event listeners should always be unregistered when they are no longer needed in a program. The following code demonstrates the use of removeEventListener( ); it stops mouseMoveListener( ) from receiving notification of MouseEvent.MOUSE_MOVE events targeted at the Stage instance: stage.removeEventListener(MouseEvent.MOUSE_MOVE, mouseMoveListener); For more information on important event-related memory issues, see the section “Event Listeners and Memory Management” later in this chapter. Accessing the Target Object | 209 Event Vocabulary Review The following list of terms reviews the key event vocabulary we’ve encountered so far: Event Conceptually, something that has happened (some “asynchronous occur- rence”), such as a mouse click or the completion of a load operation. Each event is identified by an event name, which is typically accessible via a constant. Con- stants for the built-in events are defined either by the Event class or by the Event subclass most closely related to the event. Event object An object representing a specific single occurrence of an event. The event object’s class determines what information about the event is available to event listeners. All event objects are instances either of the Event class or of one of its subclasses. Event target The object to which the event conceptually pertains. Acts as the destination object of a dispatched event, as determined uniquely by each type of event. Every event target (and target ancestor in the case of targets on the display list) can register event listeners to be notified when an event occurs. Event listener A function or method registered to receive event notification from an event tar- get (or from an event target’s ancestor in the case of targets on the display list). Event dispatching Sending notification of the event to the event target, which triggers registered lis- teners. (If the target is on the display list, the event dispatch proceeds through the event flow, from the root of the display list to the target, and, for bubbling events, back to the root. (See Chapter 21 for information on the display list and the event flow.) Event dispatching is also known as event propagation. Looking ahead, here’s a little more event vocabulary that we’ll encounter in future event-handling discussions: listeners executed in response to an event are said to have been triggered by that event. Once a triggered listener has finished executing, it is said to have processed the event. Once all of an object’s listeners have processed a given event, the object itself is said to have finished processing the event. Now that we’re familiar with the basics of events and event handling, let’s take a deeper look at a variety of specific event-handling topics. Accessing the Target Object During every event dispatch, the Event object passed to every event listener defines a target variable that provides a reference to the target object. Hence, to access the tar- 210 | Chapter 12: Events and Event Handling get of an event dispatch, we use the following general event-listener code, which sim- ply outputs the event target’s String value during debugging: public function someListener (e:SomeEvent):void { // Access the target of the event dispatch trace(e.target); } Programs typically use the Event class’s instance variable target to control the target object in some way. For example, recall the code we used to respond to the comple- tion of a file-load operation (shown in Example 12-1): package { import flash.display.*; import flash.net.*; import flash.events.*; public class FileLoader extends Sprite { public function FileLoader ( ) { var urlLoader:URLLoader = new URLLoader( ); urlLoader.addEventListener(Event.COMPLETE, completeListener); urlLoader.load(new URLRequest("someFile.txt")); } private function completeListener (e:Event):void { trace("Load complete"); } } } In the preceding code, within the completeListener( ) function, we might want to access the urlLoader object in order to retrieve the content of the loaded file. Here’s the code we’d use (notice that, for added type safety, we cast target to URLLoader— the actual datatype of the target object): private function completeListener (e:Event):void { var loadedText:String = URLLoader(e.target).data; } After the preceding code runs, loadedText’s value is the contents of the loaded text file (someFile.txt). Example 12-3 provides another example of accessing an event’s target object, this time for a target object that is on the display list. In the example, we set a text field’s background color to red when it has focus. To access the TextField, the focusInListener( ) method uses the Event class’s instance variable target variable. Example 12-3 uses several techniques that we haven’t yet covered— creating text, focusing an object, working with the display list, and the event flow. We’ll study each of those topics in Part II of this book. If you are new to display programming, consider skipping this example and returning to it after you have read Part II. Accessing the Target Object | 211 Reader exercise: try adding a FocusEvent.FOCUS_OUT listener to Example 12-3 that changes the text field’s background color to white. Example 12-3. Accessing the target object package { import flash.display.*; import flash.events.*; import flash.text.*; // Changes a text field's background color to red when focused public class HighlightText extends Sprite { // Constructor public function HighlightText ( ) { // Create a Sprite object var s:Sprite = new Sprite( ); s.x = 100; s.y = 100; // Create a TextField object var t:TextField = new TextField( ); t.text = "Click here"; t.background = true; t.border = true; t.autoSize = TextFieldAutoSize.LEFT; // Put the TextField in the Sprite s.addChild(t); // Add the Sprite to this object's display hierarchy addChild(s); // Register to be notified when the user focuses any of the Sprite // object's descendants (in this case, there's only one descendant: // the TextField, t) s.addEventListener(FocusEvent.FOCUS_IN, focusInListener); } // Listener executed when one of the Sprite object's descendants // is focused public function focusInListener (e:FocusEvent):void { // Displays: Target of this event dispatch: [object TextField] trace("Target of this event dispatch: " + e.target); // Set the text field's background to red. Notice that, for added type // safety, we cast Event.target to TextField-—the actual datatype of // the target object. TextField(e.target).backgroundColor = 0xFF0000; } } } 212 | Chapter 12: Events and Event Handling Accessing the Object That Registered the Listener During every event dispatch, the Event object passed to every event listener defines a currentTarget variable that provides a reference to the object with which the event listener registered. The following general event-listener code demonstrates; it out- puts the String value of the object with which someListener( ) registered: public function someListener (e:SomeEvent):void { // Access the object with which this event listener registered trace(e.currentTarget); } For events targeted at nondisplay objects, the value of the Event class’s instance vari- able currentTarget is always equal to target (because listeners always register with the event target). For example, returning once again to the FileLoader class from Example 12-1, if we check the value of both e.currentTarget and e.target within completeListener( ), we find that those two variables refer to the same object: package { import flash.display.*; import flash.net.*; import flash.events.*; public class FileLoader extends Sprite { public function FileLoader ( ) { var urlLoader:URLLoader = new URLLoader( ); urlLoader.addEventListener(Event.COMPLETE, completeListener); urlLoader.load(new URLRequest("someFile.txt")); } private function completeListener (e:Event):void { trace(e.currentTarget == e.target); // Displays: true } } } However, as we’ll learn in Chapter 21, for events targeted at display objects in a dis- play hierarchy, listeners can register both with the event target and with the event target’s display ancestors. For event listeners registered with an event target’s display ancestor, currentTarget refers to that display ancestor, while target refers to the event target object. For example, suppose a Sprite object that contains a TextField object registers a MouseEvent.CLICK event listener, clickListener( ). When the user clicks the text field, a MouseEvent.CLICK event is dispatched, and clickListener( ) is triggered. Within clickListener( ), currentTarget refers to the Sprite object, while target refers to the TextField object. Programs typically use currentTarget to control the object that registered a listener in some way. As an applied example, let’s revise the focusInListener( ) function from Example 12-3. This time, when the TextField object is focused, our new Preventing Default Event Behavior | 213 focusInListener( ) function will display a blue oval behind the text field. The blue oval is drawn in the Sprite object, which is accessed via currentTarget. public function focusInListener (e:FocusEvent):void { // Set the text field's background to red TextField(e.target).backgroundColor = 0xFF0000; // Obtain a reference to the Sprite object var theSprite:Sprite = Sprite(e.currentTarget); // Draw the ellipse in the Sprite object theSprite.graphics.beginFill(0x0000FF); theSprite.graphics.drawEllipse(-10, -10, 75, 40); } Preventing Default Event Behavior Some events in ActionScript are associated with a side effect known as a default behavior. For example, the default behavior of a TextEvent.TEXT_INPUT event is text being added to the target text field. Likewise, the default behavior for a MouseEvent. MOUSE_DOWN event targeted at a SimpleButton object displays the button’s “down state” graphic. In some cases, events with a default behavior offer the option to prevent that behav- ior programmatically. Events with a default behavior that can be prevented are said to be cancelable. For example, the TextEvent.TEXT_INPUT event is cancelable, as are FocusEvent.KEY_FOCUS_CHANGE and FocusEvent.MOUSE_FOCUS_CHANGE. To prevent the default behavior for a cancelable event, we invoke the Event class’s instance method preventDefault( ) on the Event object passed to any listener regis- tered for that event. For example, in the following code, we prevent the default behavior for all TextEvent.TEXT_INPUT events targeted at the text field t. Instead of allowing the user-entered text to appear in the text field, we simply add the letter “x” to the text field. package { import flash.display.*; import flash.text.*; import flash.events.*; // Changes all user-entered text to the character "x" public class InputConverter extends Sprite { private var t:TextField; public function InputConverter ( ) { // Create the text field t = new TextField( ); t.border = true; t.background = true; t.type = TextFieldType.INPUT addChild(t); 214 | Chapter 12: Events and Event Handling // Register for the TextEvent.TEXT_INPUT event t.addEventListener(TextEvent.TEXT_INPUT, textInputListener); } // Listener executed when the TextEvent.TEXT_INPUT event occurs private function textInputListener (e:TextEvent):void { // Show what the user tried to enter trace("Attempted text input: " + e.text); // Stop the user-entered text from appearing in the text field e.preventDefault( ); // Add the letter "x" to the text field instead of // the user-entered text t.appendText("x"); } } } To determine whether a given event has default behavior that can be canceled, check the value of Event class’s instance variable cancelable within a listener registered for that event. For built-in events, see also the event’s entry in Adobe ActionScript Lan- guage Reference. To determine whether an event currently being dispatched has had its default behav- ior prevented, check the return value of the Event class’s instance method isDefaultPrevented( ) within a listener registered for that event. Note that just like built-in events, custom events can define default behavior that can be canceled via preventDefault( ). For more information and example code, see the section “Preventing Default Behavior for Custom Events” later in this chapter. For another example showing how to use preventDefault( ) with the TextEvent.TEXT_INPUT event, see Example 22-8 in Chapter 22. Event Listener Priority By default, when multiple event listeners are registered for a single event type with a given object, those listeners are triggered in the order in which they registered. For example, in the following code two event listeners—completeListenerA( ) and completeListenerB( )—register with urlLoader for the Event.COMPLETE event. When the Event.COMPLETE event occurs, completeListenerA( ) executes before completeListenerB( ) because completeListenerA( ) registered before completeListenerB( ). package { import flash.display.*; import flash.net.*; import flash.events.*; public class FileLoader extends Sprite { public function FileLoader ( ) { Event Listener Priority | 215 var urlLoader:URLLoader = new URLLoader( ); // Registration order determines execution order urlLoader.addEventListener(Event.COMPLETE, completeListenerA); urlLoader.addEventListener(Event.COMPLETE, completeListenerB); urlLoader.load(new URLRequest("someFile.txt")); } private function completeListenerA (e:Event):void { trace("Listener A: Load complete"); } private function completeListenerB (e:Event):void { trace("Listener B: Load complete"); } } } To alter the default order in which event listeners are triggered, we can use the addEventListener( ) method’s priority parameter, shown in the following generic code: addEventListener(type, listener, useCapture, priority, useWeakReference) The priority parameter is an integer indicating the order in which the event listener being registered should be triggered, relative to other listeners registered for the same event with the same object. Listeners registered with a higher priority are triggered before listeners registered with a lower priority. For example, a listener registered with priority 3 will be triggered before a listener registered with priority 2. When two listeners are registered with the same priority, they are executed in the order in which they were registered. When priority is not specified, it defaults to 0. The following code demonstrates the general use of the priority parameter; it forces completeListenerB() to execute before completeListenerA() even though completeListenerA() registers before completeListenerB(). package { import flash.display.*; import flash.net.*; import flash.events.*; public class FileLoader extends Sprite { public function FileLoader ( ) { var urlLoader:URLLoader = new URLLoader( ); // Priority parameter determines execution order urlLoader.addEventListener(Event.COMPLETE, completeListenerA, false, 0); urlLoader.addEventListener(Event.COMPLETE, completeListenerB, false, 1); urlLoader.load(new URLRequest("someFile.txt")); } 216 | Chapter 12: Events and Event Handling private function completeListenerA (e:Event):void { trace("Listener A: Load complete"); } private function completeListenerB (e:Event):void { trace("Listener B: Load complete"); } } } The priority parameter is rarely needed, but can prove useful in specific situations. For example, an application framework might use a high priority listener to perform initialization on a loaded application before other listeners have a chance to execute. Or a testing suite might use a high priority listener to disable other listeners that would otherwise interfere with a given test (see the section “Stopping an Event Dispatch” in Chapter 21). Use caution when altering event listener execution order. Programs that depend on an execution order are prone to error because event lis- tener priorities are volatile, difficult to maintain, and make source code more difficult to follow. Event Listeners and Memory Management As we’ve seen throughout this chapter, ActionScript’s event architecture is based on two key participants: the listener (either a function or a method) and the object with which that listener registers. Each object that registers a listener for a given event keeps track of that listener by assigning a reference to it in an internal array known as a listener list. For example, in the following code (repeated from Example 12-1) the completeListener( ) method registers with urlLoader for Event.COMPLETE events. As a result, urlLoader’s internal listener list gains a reference to completeListener( ). package { import flash.display.*; import flash.net.*; import flash.events.*; public class FileLoader extends Sprite { public function FileLoader ( ) { var urlLoader:URLLoader = new URLLoader( ); // Register completeListener( ) urlLoader.addEventListener(Event.COMPLETE, completeListener); urlLoader.load(new URLRequest("someFile.txt")); } private function completeListener (e:Event):void { trace("Load complete"); } } } Event Listeners and Memory Management | 217 By default, any object that has a reference to a listener maintains that reference until the listener is explicitly unregistered via the removeEventListener( ) method. Further- more, the object maintains its reference to the listener even when no other refer- ences to the listener remain in the program. The following simple class, AnonymousListener, demonstrates. It creates an anonymous function and registers that function for MouseEvent.MOUSE_MOVE events with the Flash client runtime’s Stage instance. Even though the AnonymousListener class has no references to the anony- mous function, the function is permanently retained by the Stage instance, and con- tinues to be triggered every time the MouseEvent.MOUSE_MOVE occurs, long after the AnonymousListener constructor method exits. package { import flash.display.*; import flash.events.*; public class AnonymousListener extends Sprite { public function AnonymousListener ( ) { // Adds an anonymous function to the Stage instance's // listener list stage.addEventListener(MouseEvent.MOUSE_MOVE, function (e:MouseEvent):void { trace("mouse move"); }); } } } In the preceding code, the anonymous function is permanently stranded in the Stage instance’s listener list. The program cannot unregister the anonymous function because it has no reference to that function. Stranded listeners are a potential source of serious memory waste and can cause other problematic side effects in ActionScript programs. Let’s consider an applied example demonstrating the potential risks of stranding lis- teners, and ways to avoid those risks. Suppose we’re building a butterfly-catching game in which the player catches butter- flies by touching them with the mouse. Butterflies try to avoid being caught by flying away from the mouse pointer. The application’s main class is ButterflyGame. Each butterfly is represented by an instance of the Butterfly class. To manage butterfly movement, the game uses a central Timer object that triggers a TimerEvent.TIMER event every 25 milliseconds. Each Butterfly object registers a listener with the central Timer object, and calculates a new position for itself every time a TimerEvent.TIMER event occurs. 218 | Chapter 12: Events and Event Handling Here’s the code for the Butterfly class: package { import flash.display.*; import flash.events.*; import flash.utils.*; public class Butterfly extends Sprite { // Each Butterfly object receives a reference to the central // timer through the gameTimer constructor parameter public function Butterfly (gameTimer:Timer) { gameTimer.addEventListener(TimerEvent.TIMER, timerListener); } private function timerListener (e:TimerEvent):void { trace("Calculating new butterfly position..."); // Calculate new butterfly position (code not shown) } } } And here’s the code for the ButterflyGame class, highly simplified to isolate the but- terfly creation and removal code. In this version of the code, the game contains one butterfly only. package { import flash.display.*; import flash.utils.*; public class ButterflyGame extends Sprite { private var timer:Timer; private var butterfly:Butterfly; public function ButterflyGame ( ) { // The game timer timer = new Timer(25, 0); timer.start( ); addButterfly( ); } // Adds the butterfly to the game public function addButterfly ( ):void { butterfly = new Butterfly(timer); } // Removes the butterfly from the game public function removeButterfly ( ):void { butterfly = null; } } } To add the butterfly to the game, ButterflyGame uses the following code: butterfly = new Butterfly(timer); Event Listeners and Memory Management | 219 When that code runs, the Butterfly constructor runs, and the Butterfly object’s timerListener( ) method registers with gameTimer for TimerEvent.TIMER events. When the player catches the butterfly, ButterflyGame removes the Butterfly object from the program using the following code: butterfly = null; However, even though the preceding code removes ButterflyGame’s reference to the Butterfly object, gameTimer’s listener list retains its reference to the Butterfly object’s timerListener( ) method—and, by extension, to the Butterfly object itself. Further- more, timerListener( ) continues to execute every time a TimerEvent.TIMER occurs. The Butterfly object, hence, continues to consume memory and processor time, and has the potential to trigger unexpected or unwanted side effects in the program. To avoid these problems, when we remove a Butterfly object from our game, we must first unregister its timerListener( ) method for TimerEvent.TIMER events. In order to facilitate TimerEvent.TIMER event unregistration, let’s add a new variable, gameTimer, and a new method, destroy( ), to the Butterfly class. The central game timer is assigned to the gameTimer variable. The destroy( ) method unregisters timerListener( ) for TimerEvent.TIMER events. Here’s the updated Butterfly class, with additions shown in bold: package { import flash.display.*; import flash.events.*; import flash.utils.*; public class Butterfly extends Sprite { private var gameTimer:Timer; public function Butterfly (gameTimer:Timer) { this.gameTimer = gameTimer; this.gameTimer.addEventListener(TimerEvent.TIMER, timerListener); } private function timerListener (e:TimerEvent):void { trace("Calculating new butterfly position..."); // Calculate new butterfly position (code not shown) } public function destroy ( ):void { gameTimer.removeEventListener(TimerEvent.TIMER, timerListener); } } } In the ButterflyGame class’s instance method removeButterfly( ), we invoke destroy( ) before removing the reference to the Butterfly object, as follows: public function removeButterfly ( ):void { butterfly.destroy( ); 220 | Chapter 12: Events and Event Handling butterfly = null; } By invoking destroy( ) before removing the Butterfly object from the game, we pre- vent timerListener( ) from being stranded in the Timer object’s listener list. When you register an event listener with an object, be sure your pro- gram also eventually unregisters that listener. Weak Listener References In the preceding section we learned that, by default, an object that registers a listener for a given event maintains a reference to that listener until it is explicitly unregistered for that event—even when no other references to the listener remain in the program. This default behavior can, however, be altered with addEventListener( )’s useWeakReference parameter. This topic requires a prior understanding of garbage collection in ActionScript, which is covered in Chapter 14. Registering a listener with useWeakReference set to true prevents that listener from becoming stranded in the listener list of the object with which it registered. For example, suppose an object, O, registers a listener, L, for an event, E, with useWeakReference set to true. Further suppose that the only reference the program has to L is the one held by O. Normally, L would be held by O until L is unregistered for the event E. However, because L originally registered with useWeakReference set to true, and because O holds the only remaining reference to L in the program, L imme- diately becomes eligible for garbage collection. Subsequently, the garbage collector, at its discretion, can choose to automatically remove L from O’s listener list, and delete it from memory. To demonstrate useWeakReference, let’s return to the AnonymousListener class. Recall that AnonymousListener creates an anonymous function and registers that function for MouseEvent.MOUSE_MOVE events with the Flash client runtime’s Stage instance. This time, however, when we register the function for MouseEvent.MOUSE_MOVE events, we set useWeakReference set to true. package { import flash.display.*; import flash.events.*; public class AnonymousListener extends Sprite { public function AnonymousListener ( ) { // Add an anonymous function to the Stage instance's // listener list Custom Events | 221 stage.addEventListener(MouseEvent.MOUSE_MOVE, function (e:MouseEvent):void { trace("mouse move"); }, false, 0, true); } } } When the preceding code runs, the program’s only reference to the anonymous func- tion is the one held by the Stage instance. Because the anonymous function was reg- istered with useWeakReference set to true, it immediately becomes eligible for garbage collection. Hence, the garbage collector, at its discretion, can subsequently choose to automatically remove the anonymous function from the Stage instance’s listener list, and delete it from memory. Of course, just because the anonymous function is eligible for garbage collection does not mean it will be garbage collected. In fact, in the case of the preceding sim- ple example, the function will likely not be garbage collected because the application does not use enough memory to trigger a garbage collection. As a result, the func- tion will continue to be executed anytime a MouseEvent.MOUSE_MOVE event dispatch targets the Stage instance, even though theoretically it could be garbage collected at any time. Hence, in general, useWeakReference should not be relied on as a way to automatically remove event listeners. As a best practice, simply avoid stranding event listeners. A previous note can’t be emphasized enough, so it bears repeating: When you register an event listener with an object, be sure your pro- gram also eventually unregisters that listener. So far in this chapter we’ve worked exclusively with ActionScript’s built-in events. Now let’s consider how to implement our own custom events in a program. Custom Events Dispatching a new custom event in ActionScript is as simple as extending the EventDispatcher class, giving the new event a name, and invoking the EventDispatcher class’s instance method dispatchEvent( ). To learn how to create custom events in a program, we’ll study two examples: first, an event in a game, and then an event for a user interface widget. 222 | Chapter 12: Events and Event Handling To target an event dispatch at an instance of a class that already extends a class another, use the composition approach discussed in Chapter 9: implement the IEventDispatcher interface directly, and use EventDispatcher’s services via composition rather than inheritance. A Custom “gameOver” Event Suppose we’re creating a general framework for video game development. The framework includes the following two classes: Game, which handles the basic needs of any video game; and Console, which represents a launchpad from which to start new games. The Console class instantiates a Game object whenever a new game is started. Each Game class instance created by the Console class is the target of a cus- tom “gameOver” event, which is dispatched when a game ends. In order to allow Game objects to act as event targets, the Game class extends EventDispatcher, as follows: package { import flash.events.*; public class Game extends EventDispatcher { } } The Game class also defines a constant, Game.GAME_OVER, whose value is the name of the custom event: gameOver. By convention, event constants are written with all capital letters, and words separated by an underscore, as in GAME_OVER. Custom event constants are typically defined either by the event target class (in this case, Game) or, if an Event subclass is used, by that Event subclass (as shown in our upcoming wid- get example). Our present game example does not include an Event subclass, so we define the event constant for gameOver in the Game class, as follows: package { import flash.events.*; public class Game extends EventDispatcher { public static const GAME_OVER:String = "gameOver"; } } When a game is over, the Game object calls the endGame( ) method, which resets the game environment and prepares for the possibility of a new game. Here’s the endGame( ) method: package { import flash.events.*; public class Game extends EventDispatcher { public static const GAME_OVER:String = "gameOver"; Custom Events | 223 private function endGame ( ):void { // Perform game-ending duties (code not shown) } } } When all game-ending duties are complete, endGame( ) uses dispatchEvent( ) to dis- patch a Game.GAME_OVER event signaling the end of the game: package { import flash.events.*; public class Game extends EventDispatcher { public static const GAME_OVER:String = "gameOver"; private function endGame ( ):void { // Perform game-ending duties (code not shown)... // ...then ask ActionScript to dispatch an event indicating that // the game is over dispatchEvent(new Event(Game.GAME_OVER)); } } } Note that because dispatchEvent( ) is invoked on the Game object, that object is the target of the event. The object on which dispatchEvent( ) is invoked is the event target. The dispatchEvent( ) method shown in the preceding code takes a single parameter— an Event object representing the event being dispatched. The Event constructor, itself, it takes three parameters—type, bubbles, and cancelable as shown in the fol- lowing generalized code: Event(type, bubbles, cancelable) In most cases, however, only the first argument, type, is needed; it specifies the string name of the event (in our case, Game.GAME_OVER). The bubbles parameter is used when the event target is a display object only; it indicates whether the event flow should include a bubbling phase (true) or not (false). (See the section “Custom Events and the Event Flow” in Chapter 21.) The cancelable parameter is used to create custom events with preventable default behavior, as discussed later, in the section “Prevent- ing Default Behavior for Custom Events.” To register an event listener for our custom Game.GAME_OVER event, we use addEventListener( ), just as we would when registering for a built-in event. For exam- ple, suppose that, when a game ends, we want the Console class to display a dialog boxthat gives the player the option to return to the launchpad or to play the current 224 | Chapter 12: Events and Event Handling game again. In the Console class, we detect the ending of a game by registering for Game.GAME_OVER events, as follows: package { import flash.display.*; import flash.events.*; public class Console extends Sprite { // Constructor public function Console ( ) { var game:Game = new Game( ); game.addEventListener(Game.GAME_OVER, gameOverListener); } private function gameOverListener (e:Event):void { trace("The game has ended!"); // Display "back to console" UI (code not shown) } } } Notice that the datatype of the event object passed to gameOverListener( ) matches the datatype of the event object originally passed to dispatchEvent( ) in the Game class’s instance method endGame( ) (shown in the previous code). When creating a listener for a custom event, set the datatype of the lis- tener’s parameter to match the datatype of the event object originally passed to dispatchEvent( ). Example 12-4 shows the code for our custom Game.GAME_OVER event in its entirety, and adds a timer that forces a call to endGame( ), simulating the ending of an actual game. (For details on the Timer class, see Adobe’s ActionScript Language Reference.) Example 12-4. A custom “gameOver” event // The Game class (the event target) package { import flash.events.*; import flash.utils.*; // Required for the Timer class public class Game extends EventDispatcher { public static const GAME_OVER:String = "gameOver"; public function Game ( ) { // Force the game to end after one second var timer:Timer = new Timer(1000, 1); timer.addEventListener(TimerEvent.TIMER, timerListener); timer.start( ); // A nested function that is executed one second after this object // is created function timerListener (e:TimerEvent):void { Custom Events | 225 Now let’s take a look at another example, an event for a user interface widget. A Custom “toggle” Event Suppose we’re creating a toggle-switch widget with an on and off state. Our toggle switch is represented by the ToggleSwitch class. Whenever the switch is toggled on or off, we dispatch a custom event named “toggle.” In the preceding section, the event object for our custom Game.GAME_OVER event was an instance of the built-in Event class. This time, our custom event will be represented by its own class, ToggleEvent. The ToggleEvent class has the following two purposes: • Define the constant for the toggle event (ToggleEvent.TOGGLE) • Define a variable, isOn, which listeners use to determine the state of the target ToggleSwitch object endGame( ); } } private function endGame ( ):void { // Perform game-ending duties (code not shown)... // ...then ask ActionScript to dispatch an event indicating that // the game is over dispatchEvent(new Event(Game.GAME_OVER)); } } } // The Console class (registers a listener for the event) package { import flash.display.*; import flash.events.*; public class Console extends Sprite { // Constructor public function Console ( ) { var game:Game = new Game( ); game.addEventListener(Game.GAME_OVER, gameOverListener); } private function gameOverListener (e:Event):void { trace("The game has ended!"); // Display "back to console" UI (code not shown) } } } Example 12-4. A custom “gameOver” event (continued) 226 | Chapter 12: Events and Event Handling The code for the ToggleEvent class follows. Note that every custom Event subclass must override both clone( ) and toString( ), providing versions of those methods that account for any custom variables in the subclass (e.g., isOn). The toggle switch code in this section focuses solely on the implementation of the toggle event; the code required to create interactivity and graphics is omitted. package { import flash.events.*; // A class representing the custom "toggle" event public class ToggleEvent extends Event { // A constant for the "toggle" event type public static const TOGGLE:String = "toggle"; // Indicates whether the switch is now on or off public var isOn:Boolean; // Constructor public function ToggleEvent (type:String, bubbles:Boolean = false, cancelable:Boolean = false, isOn:Boolean = false) { // Pass constructor parameters to the superclass constructor super(type, bubbles, cancelable); // Remember the toggle switch's state so it can be accessed within // ToggleEvent.TOGGLE listeners this.isOn = isOn; } // Every custom event class must override clone( ) public override function clone( ):Event { return new ToggleEvent(type, bubbles, cancelable, isOn); } // Every custom event class must override toString( ). Note that // "eventPhase" is an instance variable relating to the event flow. // See Chapter 21. public override function toString( ):String { return formatToString("ToggleEvent", "type", "bubbles", "cancelable", "eventPhase", "isOn"); } } } Next, let’s turn to the ToggleSwitch class, which represents the toggle switch. The ToggleSwitch class’s sole method, toggle( ), changes the state of the toggle switch, and then dispatches a ToggleEvent.TOGGLE event indicating that the switch’s state has changed. The following code shows the ToggleSwitch class. Notice that the ToggleSwitch class extends Sprite, which provides support for onscreen display. As a Custom Events | 227 descendant of EventDispatcher, the Sprite class also provides the required event- dispatching capabilities: package { import flash.display.*; import flash.events.*; // Represents a simple toggle-switch widget public class ToggleSwitch extends Sprite { // Remembers the state of the switch private var isOn:Boolean; // Constructor public function ToggleSwitch ( ) { // The switch is off by default isOn = false; } // Turns the switch on if it is currently off, or off if it is // currently on public function toggle ( ):void { // Toggle the switch state isOn = !isOn; // Ask ActionScript to dispatch a ToggleEvent.TOGGLE event, targeted // at this ToggleSwitch object dispatchEvent(new ToggleEvent(ToggleEvent.TOGGLE, true, false, isOn)); } } } To demonstrate the use of the ToggleEvent.TOGGLE event, let’s create a simple exam- ple class, SomeApp. The SomeApp class defines a method, toggleListener( ), and regis- ters it with a ToggleSwitch object for ToggleEvent.TOGGLE events. For demonstration purposes, the SomeApp class also programmatically toggles the switch on, triggering a ToggleEvent.TOGGLE event. package { import flash.display.*; // A generic application that demonstrates the use of the custom // ToggleEvent.TOGGLE event public class SomeApp extends Sprite { // Constructor public function SomeApp ( ) { // Create a ToggleSwitch var toggleSwitch:ToggleSwitch = new ToggleSwitch( ); // Register for ToggleEvent.TOGGLE events toggleSwitch.addEventListener(ToggleEvent.TOGGLE, toggleListener); 228 | Chapter 12: Events and Event Handling // Toggle the switch (normally the switch would be toggled by the // user, but for demonstration purposes, we toggle // it programmatically) toggleSwitch.toggle( ); } // Listener executed whenever a ToggleEvent.TOGGLE event occurs private function toggleListener (e:ToggleEvent):void { if (e.isOn) { trace("The ToggleSwitch is now on."); } else { trace("The ToggleSwitch is now off."); } } } } Now that we have some experience implementing custom events, let’s consider an advanced scenario: a custom event type with a default behavior. Preventing Default Behavior for Custom Events In the earlier section, “Preventing Default Event Behavior,” we saw that some built-in events are associated with a default behavior. For example, the TextEvent.TEXT_INPUT event is associated with the default behavior of adding text to a text field. We also saw that, for built-in events that are classified as cancelable, the default behavior can be prevented using the Event class’s instance method preventDefault( ) method. Custom events can also be associated with custom default behavior that can likewise be prevented via preventDefault( ). A custom event’s default behavior is entirely pro- gram determined and implemented. The general approach for implementing events associated with preventable default behavior is as follows: 1. At event-dispatch time, create an event object representing the event, making sure to set the Event constructor’s cancelable parameter to true. 2. Use dispatchEvent( ) to dispatch the event. 3. After dispatchEvent( ) completes, use the Event class’s instance method isDefaultPrevented( ) to check whether any listeners requested the prevention of the default behavior. 4. If the event object’s isDefaultPrevented( ) method returns false, then proceed with the default behavior; otherwise, do not carry out the default behavior. Here’s the generic code for implementing an event with a preventable default behavior: // Create the event object, with the desired values for type and // bubbles. Set cancelable (the third parameter) to true. var e:Event = new Event(type, bubbles, true); Custom Events | 229 // Dispatch the event dispatchEvent(e); // Check whether any listeners requested the prevention of the // default behavior. If no listener called preventDefault( )... if (!e.isDefaultPrevented( )) { // ...then carry out the default behavior } Let’s apply these steps to an example that builds on the toggle switch example. Sup- pose we’re using our toggle switch widget in a control-panel application that assigns different privileges to its users depending on their status. Guest users are prevented from using some of the switches in the panel, while administrative users are allowed to use all switches in the panel. To accommodate the different user levels in the application, we define a new toggle switch event type: ToggleEvent.TOGGLE_ATTEMPT. The ToggleEvent.TOGGLE_ATTEMPT occurs anytime the user attempts to turn a toggle switch on or off. The default behavior associated with the ToggleEvent.TOGGLE_ATTEMPT event is the toggling of the switch. For the sake of simplicity, we’ll assume that our toggle switch can only be turned on or off via a mouse click (not via the keyboard). Whenever the user clicks the toggle switch, we dispatch a ToggleEvent.TOGGLE_ATTEMPT. Then, if no listener prevents the default behavior, we carry out the toggle request. Here’s the relevant code: private function clickListener (e:MouseEvent):void { // The user has attempted to turn the switch on or off, so ask // ActionScript to dispatch a ToggleEvent.TOGGLE_ATTEMPT event, // targeted at this ToggleSwitch object. First create the event object... var toggleEvent:ToggleEvent = new ToggleEvent(ToggleEvent.TOGGLE_ATTEMPT, true, true); // ... then request the event dispatch dispatchEvent(toggleEvent); // The ToggleEvent.TOGGLE_ATTEMPT event dispatch is now complete. // If no listener prevented the default event behavior... if (!toggleEvent.isDefaultPrevented( )) { // ...then toggle the switch toggle( ); } } In our control-panel application, we register a ToggleEvent.TOGGLE_ATTEMPT listener for every ToggleSwitch object. Within that listener, we evaluate the user’s status. For restricted switches, if the user is a guest, we prevent the default behavior. Here’s the code: // Listener executed whenever a ToggleEvent.TOGGLE_ATTEMPT event occurs private function toggleAttemptListener (e:ToggleEvent):void { 230 | Chapter 12: Events and Event Handling // If the user is a guest... if (userType == UserType.GUEST) { // ...deny the attempted use of the toggle switch e.preventDefault( ); } } Example 12-5 shows the control-panel application in its entirety, complete with a fully functioning, albeit simple, graphical version of the toggle switch widget. The comments will guide you through the code. Example 12-5. The control panel application classes // The ToggleEvent class package { import flash.events.*; // A class representing the custom "toggle" event public class ToggleEvent extends Event { // A constant for the "toggle" event type public static const TOGGLE:String = "toggle"; // A constant for the "toggleAttempt" event type public static const TOGGLE_ATTEMPT:String = "toggleAttempt"; // Indicates whether the switch is now on or off public var isOn:Boolean; // Constructor public function ToggleEvent (type:String, bubbles:Boolean = false, cancelable:Boolean = false, isOn:Boolean = false) { // Pass constructor parameters to the superclass constructor super(type, bubbles, cancelable); // Remember the toggle switch's state so it can be accessed within // ToggleEvent.TOGGLE listeners this.isOn = isOn; } // Every custom event class must override clone( ) public override function clone( ):Event { return new ToggleEvent(type, bubbles, cancelable, isOn); } // Every custom event class must override toString( ). public override function toString( ):String { return formatToString("ToggleEvent", "type", "bubbles", "cancelable", "eventPhase", "isOn"); } } } Custom Events | 231 // The ToggleSwitch class package { import flash.display.*; import flash.events.*; // Represents a simple toggle-switch widget with preventable default // behavior public class ToggleSwitch extends Sprite { // Remembers the state of the switch private var isOn:Boolean; // Contains the toggle switch graphics private var icon:Sprite; // Constructor public function ToggleSwitch ( ) { // Create the Sprite to contain the toggle switch graphics icon = new Sprite( ); addChild(icon); // Set the switch to off by default isOn = false; drawOffState( ); // Register to be notified when the user clicks the switch graphic icon.addEventListener(MouseEvent.CLICK, clickListener); } // Listener executed when the user clicks the toggle switch private function clickListener (e:MouseEvent):void { // The user has attempted to turn the switch on or off, so ask // ActionScript to dispatch a ToggleEvent.TOGGLE_ATTEMPT event, // targeted at this ToggleSwitch object. First create the event // object... var toggleEvent:ToggleEvent = new ToggleEvent(ToggleEvent.TOGGLE_ATTEMPT, true, true); // ...then request the event dispatch dispatchEvent(toggleEvent); // The ToggleEvent.TOGGLE_ATTEMPT event dispatch is now complete. // If no listener prevented the default event behavior... if (!toggleEvent.isDefaultPrevented( )) { // ...then toggle the switch toggle( ); } } // Turns the switch on if it is currently off, or off if it is // currently on. Note that the switch can be toggled programmatically, // even if the user does not have privileges to toggle it manually. public function toggle ( ):void { // Toggle the switch state isOn = !isOn; Example 12-5. The control panel application classes (continued) 232 | Chapter 12: Events and Event Handling // Draw the matching graphic for the new switch state if (isOn) { drawOnState( ); } else { drawOffState( ); } // Ask ActionScript to dispatch a ToggleEvent.TOGGLE event, targeted // at this ToggleSwitch object var toggleEvent:ToggleEvent = new ToggleEvent(ToggleEvent.TOGGLE, true, false, isOn); dispatchEvent(toggleEvent); } // Draws the graphics for the off state private function drawOffState ( ):void { icon.graphics.clear( ); icon.graphics.lineStyle(1); icon.graphics.beginFill(0xFFFFFF); icon.graphics.drawRect(0, 0, 20, 20); } // Draws the graphics for the on state private function drawOnState ( ):void { icon.graphics.clear( ); icon.graphics.lineStyle(1); icon.graphics.beginFill(0xFFFFFF); icon.graphics.drawRect(0, 0, 20, 20); icon.graphics.beginFill(0x000000); icon.graphics.drawRect(5, 5, 10, 10); } } } // The ControlPanel class (the application's main class) package { import flash.display.*; // A generic application that demonstrates the prevention of // default behavior for custom events public class ControlPanel extends Sprite { // Set this application user's privilege level. In this example, only // users with UserType.ADMIN privileges can use the toggle switch. private var userType:int = UserType.GUEST; // Constructor public function ControlPanel ( ) { // Create a ToggleSwitch var toggleSwitch:ToggleSwitch = new ToggleSwitch( ); // Register for ToggleEvent.TOGGLE_ATTEMPT events toggleSwitch.addEventListener(ToggleEvent.TOGGLE_ATTEMPT, toggleAttemptListener); Example 12-5. The control panel application classes (continued) Type Weakness in ActionScript’s Event Architecture | 233 Now that we’ve covered custom events in ActionScript, let’s turn our attention to two final advanced event topics. Type Weakness in ActionScript’s Event Architecture ActionScript’s listener-based event architecture involves many different partici- pants—the event listener, the object that registers the listener, the event target, the event object, and the event name. A given event dispatch (and response) succeeds only when those participants interoperate properly. In order for the participants to interoperate properly, the following basic assumptions must be met: • The event type for which the listener registered must exist • The listener, itself, must exist // Register for ToggleEvent.TOGGLE events toggleSwitch.addEventListener(ToggleEvent.TOGGLE, toggleListener); // Add the toggle switch to this object's display hierarchy addChild(toggleSwitch); } // Listener executed whenever a ToggleEvent.TOGGLE_ATTEMPT event occurs private function toggleAttemptListener (e:ToggleEvent):void { // If the user is a guest... if (userType == UserType.GUEST) { // ...deny the attempted use of the toggle switch e.preventDefault( ); } } // Listener executed whenever a ToggleEvent.TOGGLE event occurs private function toggleListener (e:ToggleEvent):void { if (e.isOn) { trace("The ToggleSwitch is now on."); } else { trace("The ToggleSwitch is now off."); } } } } // The UserType class package { // Defines constants representing levels of user privilege for the // control panel application public class UserType { public static const GUEST:int = 0; public static const ADMIN:int = 1; } } Example 12-5. The control panel application classes (continued) 234 | Chapter 12: Events and Event Handling • The listener must know how to handle the event object dispatched when the event occurs • The object that registered the listener must support the specified event type When a listener registers with an object for an event, it enters into a datatype-based contract that guarantees the first three of the preceding four assumptions. If that con- tract is not upheld, ActionScript generates a datatype error. For example, consider the following event-listener registration and definition code, which includes three intentional event-listener-contract violations (shown in bold): urlLoader.addEventListener(Event.COMPLTE, completeListenr); private function completeListener (e:MouseEvent):void { trace("Load complete"); } The event-listener contract violations in the preceding code are as follows: • The constant Event.COMPLTE has a typo: it is missing an “E.” ActionScript gener- ates an error warning the programmer that the event type for which the listener is attempting to register does not exist. • The event listener name, completeListenr, has a typo: another missing “e.” ActionScript generates an error warning the programmer that the listener being registered does not exist. • The datatype specified for completeListener( )’s first parameter is MouseEvent, which does not match the datatype of the event object for a Event.COMPLETE event. At event dispatch time, ActionScript generates an error warning the pro- grammer that the listener cannot handle the dispatched event object. If we were to change the preceding code to address its three datatype errors, the event dispatch and response would proceed successfully. The datatype-based contract between an event listener and the object that registers that listener helps us ensure that our event-response code runs properly. However, the contract between a listener and the object that registers that listener has a weakness: it does not guarantee that the object supports the specified event type. For example, consider the following code, which registers a listener with urlLoader for TextEvent.TEXT_INPUT events: urlLoader.addEventListener(TextEvent.TEXT_INPUT, textInputListener); Even though, in practical terms, we can safely assume that a URLLoader object will never be the target of a TextEvent.TEXT_INPUT event, the preceding code does not generate an error. In ActionScript, listeners can register for events by any name. For example, the following nonsensical code is also legal: urlLoader.addEventListener("dlrognw", dlrognwListener); Type Weakness in ActionScript’s Event Architecture | 235 While it may seem self-evident that urlLoader will never be the target of an event named “dlrognw,” it is actually possible for a program to cause such an event to be dispatched. The following code demonstrates: urlLoader.dispatchEvent(new Event("dlrognw")); To account for the possibility that a program might target an event dispatch of any event type at any object, ActionScript intentionally does not enforce the concept of “supported events.” This flexibility is the subject of some debate because it leads to potentially difficult-to-diagnose bugs in code. For example, suppose we use the Loader class to load an external image, as follows: var loader:Loader = new Loader( ); loader.load(new URLRequest("image.jpg")); Also suppose we assume that loader will be the target of load-progress events for the loading asset (much as a URLLoader object is the target of load-progress events for a loading asset). We, therefore, attempt to handle the Event.COMPLETE event for our loading asset by registering directly with the Loader object, as follows: loader.addEventListener(Event.COMPLETE, completeListener); When we run our code, we’re surprised to find that even though no errors occur, completeListener( ) is never triggered. Because no errors are generated, we have no immediate way to diagnose the problem in our code. The ensuing research and debugging costs us time and, in all likelihood, no small amount of frustration. Only by consulting Adobe’s documentation do we find the problem: Loader objects, in fact, are not the target of load-progress events; instead, load-progress events must be handled through each Loader object’s LoaderInfo instance, as follows: loader.contentLoaderInfo.addEventListener(Event.COMPLETE, completeListener); In the future, ActionScript might allow classes to declare the events they support, and corresponding compiler warnings for attempts to register for unsupported events. In the meantime, by overriding the addEventListener( ) method, classes that implement custom events can optionally throw a custom error when listeners register for unsupported events, as shown in the following code: public override function addEventListener(eventType:String, handler:Function, capture:Boolean = false, priority:int = 0, weakRef:Boolean = false):void { // The canDispatchEvent( ) method (not shown) checks the specified // eventType against the class's list of supported events, and // returns a Boolean indicating whether the specified and eventType is // a supported event if(canDispatchEvent(eventType)) { // The event is supported, so proceed with registration super.addEventListener(eventType, handler, capture, priority, weakRef); 236 | Chapter 12: Events and Event Handling } else { // The event is not supported, so throw an error throw new Error(this + " does not support events of type '" + eventType + "'" ); } } The moral of the story is: be extra cautious when registering a listener for an event. Always ensure that the object with which the listener is registered actually supports the required event. Now let’s consider one last event-architecture issue: event handling in applications comprised of .swf files from different Internet domains. The following section requires a basic understanding of .swf-file-loading techniques, covered in Chapter 28. Handling Events Across Security Boundaries In the upcoming Chapter 19, we’ll study a variety of scenarios in which security restrictions prevent one .swf file from cross-scripting (programmatically controlling) another. When two .swf files are prevented from cross-scripting each other due to Flash Player security restrictions, they are subject to the following important event- handling limitations: • Event listeners in one .swf file are forbidden from registering for events with objects in the other .swf file. • When an event-dispatch targets an object in a display hierarchy, any objects inaccessible to the target’s .swf file are not included in the event flow. Fortunately, the preceding limitations can be completely circumvented using the flash.system.Security class’s static method, allowDomain( ). Let’s consider two exam- ples showing how allowDomain( ) can be used to circumvent each of the preceding limitations. For information on loading .swf files see Chapter 28. Module.swf Listener Registers with Main.swf Object Suppose a .swf file from one site (site-a.com/Main.swf ) loads a .swf file from another site (site-b.com/Module.swf). Further suppose that Module.swf defines a listener that wishes to register with an object created by Main.swf. To permit the registration, Main.swf must execute the following line of code before Module.swf registers the listener: Security.allowDomain("site-b.com"); The preceding line of code allows all .swf files from site-b.com (including Module.swf) to register listeners with any object created by Main.swf. Handling Events Across Security Boundaries | 237 Main.Swf Listener Receives Notification for an Event Targeted at a Module.swf Display Object Continuing with the “Main.swf loads Module.swf” scenario from the preceding sec- tion, suppose Main.swf ’s main class instance adds the Loader object containing Module.swf to its display hierarchy, as follows: package { import flash.display.*; import flash.net.*; import flash.events.*; public class Main extends Sprite { private var loader:Loader; public function Main( ) { loader = new Loader( ); loader.load(new URLRequest("http://site-b.com/Module.swf")); // Add the Loader object containing Module.swf to this object's // display hierarchy addChild(loader); } } } Also suppose that Main.swf ’s main class instance wishes to be notified any time an object in Module.swf is clicked. Accordingly, Main.swf ’s main class instance regis- ters a listener with loader for MouseEvent.CLICK events, as follows: package { import flash.display.*; import flash.net.*; import flash.events.*; public class Main extends Sprite { private var loader:Loader; public function Main( ) { loader = new Loader( ); loader.load(new URLRequest("http://site-b.com/Module.swf")); addChild(loader); loader.addEventListener(MouseEvent.CLICK, clickListener); } private function clickListener (e:MouseEvent):void { trace("Module.swf was clicked"); } } } However, because Main.swf and Module.swf are from different Internet domains, security limitations prevent clickListener( ) from being triggered by MouseEvent.CLICK events targeted at loader’s display descendants (i.e., display objects in Module.swf). 238 | Chapter 12: Events and Event Handling To circumvent this limitation, Module.swf ’s main-class constructor includes the fol- lowing code: Security.allowDomain("site-a.com"); After the preceding line of code runs, Main.swf (and all .swf files from site-a.com) are trusted by Module.swf,soMain.sw f’s main class instance is included in the event flow when the Flash client runtime dispatches MouseEvent.CLICK events targeted at objects in Module.swf. As a result, clickListener( ) is triggered anytime an object in Module.swf is clicked. For complete information on allowDomain( ) and Flash Player secu- rity, see the section “Creator Permissions (allowDomain( ))” in Chapter 19. Note that calling allowDomain( ) does more than just permit event handling across security boundaries: it gives all .swf files from the permitted domain full license to cross-script the .swf file in which the allowDomain( ) invocation occurs. But there’s an alternative to allowDomain( )’s broad-based permissions. An Alternative to allowDomain( ): Shared Events In some cases, .swf files from different domains may wish to share events without allowing full cross-scripting privileges. To account for such situations, Flash Player provides the LoaderInfo class’s instance variable sharedEvents. The sharedEvents variable is a simple, neutral object through which two .swf files can pass events to each other, regardless of security restrictions. The technique allows event-based inter-.swf communication without security concessions but involves more code than the allowDomain( ) alternative. Let’s explore sharedEvents through an example scenario. Suppose Tommy runs a fire- works company with a Flash-based promotional web site, www.blast.ca. Tommy hires a contractor, Derek, to produce a self-contained mouse effect that randomly generates animated firework explosions behind the mouse pointer. Derek creates a .swf file, MouseEffect.swf, containing the effect, and posts it at www.dereksflasheffects.com/ MouseEffect.swf. Derek tells Tommy to load MouseEffect.swf into his application, www.blast.ca/BlastSite.swf. Derek and Tommy agree that MouseEffect.swf should be hosted at www.dereksflasheffects.com so that Derek can easily update the file without requiring any changes to Tommy’s web site. Tommy asks Derek to make MouseEffect.swf stop generating explosions when the mouse pointer leaves Flash Player’s display area. Derek thinks that’s a sensible idea and starts writing the appropriate code. Normally, in order to detect the mouse’s departure from Flash Player’s display area, code in MouseEffect.swf would register for Event.MOUSE_LEAVE events with the Stage instance. However, because MouseEffect.swf and BlastSite.swf come from different domains, MouseEffect.swf does not have access Handling Events Across Security Boundaries | 239 to the Stage instance. Tommy decides that, rather than give MouseEffect.swf full access to BlastSite.swf, he’ll simply forward all Event.MOUSE_LEAVE events to MouseEffect.swf via sharedEvents. Example 12-6 shows the relevant event-forwarding code from BlastSite.swf. Example 12-7 shows the relevant event-handling code from MouseEffect.swf: Example 12-6. Forwarding an event through sharedEvents package { import flash.display.*; import flash.net.*; import flash.events.*; import flash.system.*; public class BlastSite extends Sprite { private var loader:Loader; public function BlastSite ( ) { // Load MouseEffect.swf loader = new Loader( ); loader.load( new URLRequest("http://www.dereksflasheffects.com/MouseEffect.swf")); addChild(loader); // Register for Event.MOUSE_LEAVE events stage.addEventListener(Event.MOUSE_LEAVE, mouseLeaveListener); } // When Event.MOUSE_LEAVE occurs... private function mouseLeaveListener (e:Event):void { // ...forward it to MouseEffect.swf loader.contentLoaderInfo.sharedEvents.dispatchEvent(e); } } } Example 12-7. Handling an Event Targeted at sharedEvents package { import flash.display.Sprite; import flash.events.*; public class MouseEffect extends Sprite { public function MouseEffect ( ) { // Register for Event.MOUSE_LEAVE with sharedEvents loaderInfo.sharedEvents.addEventListener(Event.MOUSE_LEAVE, mouseLeaveListener); } // Handles Event.MOUSE_LEAVE events targeted at sharedEvents private function mouseLeaveListener (e:Event):void { trace("MouseEffect.mouseLeaveListener( ) was invoked..."); // Stop the explosions effect here... } 240 | Chapter 12: Events and Event Handling Derek gets paid and puts the money towards a trip to Japan. Tommy is happy with the explosion effect, although he’s not sure it has increased his sales. What’s Next? We’re making good progress in our study of ActionScript fundamentals. If you’ve read and understood the concept in the past 12 chapters, you now have enough knowledge of ActionScript to start learning about most Flash client runtime APIs. So it’s time to choose your own adventure. If you want to continue exploring Action- Script’s core features, head on to Chapter 13, where we’ll learn to write code that recovers from runtime error conditions. If, on the other hand, you’d prefer to learn how to use ActionScript to display content on screen, then skip ahead to Part II. } } Example 12-7. Handling an Event Targeted at sharedEvents (continued) 241 Chapter 13 CHAPTER 13 Exceptions and Error Handling14 In this chapter, we’ll explore ActionScript’s system for generating and responding to runtime errors—or exceptions. In ActionScript, errors can be generated both by the Flash runtime and by the program that is executing. Errors generated by the Flash runtime are known as built-in errors; errors generated by a program are known as custom errors. In a program, we can respond to, or handle, any error (whether built- in or custom) using the try/catch/finally statement; we can generate an error via the throw statement. To learn how to generate and respond to errors in a program, we’ll revisit our virtual zoo program. This chapter’s updates to the virtual zoo program are the last we’ll make until the end of this book. To complete the virtual zoo program we must discuss display programming and mouse input, both of which are covered in Part II. After you read Part II, consult the “The Final Virtual Zoo,” (Appendix) to see how to add graphics and inter- activity to the virtual zoo program. The Exception-Handling Cycle Recall that the VirtualPet class defines a setName( ) method that sets the petName vari- able of VirtualPet instances. As a refresher, here’s the relevant VirtualPet class code (portions of the class that do not pertain to setting the petName variable are omitted): public class VirtualPet { private var petName:String; public function setName (newName:String):void { // If the proposed new name has more than maxNameLength characters... if (newName.length > VirtualPet.maxNameLength) { // ...truncate it newName = newName.substr(0, VirtualPet.maxNameLength); } else if (newName == "") { // ...otherwise, if the proposed new name is an empty string, 242 | Chapter 13: Exceptions and Error Handling // then terminate this method without changing petName return; } // Assign the new, validated name to petName petName = newName; } } The setName( ) method checks whether a new pet name has a legal number of char- acters before changing the petName variable. If the new pet name is not valid, the change is not made; otherwise, the change is allowed. Let’s revise the setName( ) method so that it generates an exception (signals an error) when the newName parameter is passed an illegal number of characters. Later we’ll write some error-recovery code to handle setName( )’s new exception. To generate an exception in our code, we use the throw statement, which takes the following form: throw expression In the preceding code, expression is a data value that describes some unusual or problematic situation. Using throw to signal an error is sometimes referred to as “throwing an exception.” ActionScript allows any value to act as the expression of a throw statement. For example, the expression value could be the string literal “Some- thing went wrong!” or it could be a numeric error code. However, as a best practice, Adobe recommends using an instance of the built-in Error class (or one of its sub- classes) as the value of expression. The Error class is a standard class for represent- ing error conditions in a program. Its instance variable message is used to describe an error. The throw statement halts all currently executing code and passes expression to a special section of code known as a catch block that will respond to, or handle, the problem. Before we consider how catch blocks works, let’s rewrite setName( ) so that it generates an exception with a throw statement when an invalid petName is received. Here’s the code: public function setName (newName:String):void { // If the proposed new name has an illegal number of characters... if (newName.length > VirtualPet.maxNameLength || newName == "") { // ...generate an error throw new Error("Invalid pet name specified."); } // Assign the new valid name to petName petName = newName; } In our revised setName( ) method, when the value of newName is illegal, we use throw to halt the method’s execution rather than simply truncating the name as we did The Exception-Handling Cycle | 243 previously. We also supply a description of the problem—“Invalid pet name speci- fied”—as an argument to the Error constructor, indicating what went wrong. The Error constructor assigns that description to the new Error object’s message variable. If setName( ) encounters no problems with newName, then the method completes nor- mally, and the code that called it can rest assured that it performed its job success- fully. Otherwise, a catch block must handle the problem. A catch block is part of a larger statement known as the try/catch/finally statement. The try/catch/finally state- ment provides a backup plan for code that might throw an exception. Here’s the gen- eral structure of a typical try/catch/finally statement: try { // Code here might generate an exception } catch (e:type) { // Code here deals with the problem } finally { // Code here executes whether or not code in the try block // throws an exception } In the preceding code, the keyword try tells ActionScript that we’re about to execute some code that might generate an exception. The catch block handles exceptions generated by the try block. Code in the catch block executes if, and only if, code in the try block generates an exception. Code in the finally block always executes after either the try block or the catch block has finished. The finally block of a try/catch/ finally statement typically contains cleanup code that must execute whether or not an exception occurs in the corresponding try block. Notice the typical structure: • The try block executes code that might throw an exception. • Code in the try block uses the throw statement to indicate any errors. • If no error is thrown in the try block, then the try block executes in full, and the program skips the catch block. • If an error is thrown in the try block, the try block is aborted, and the catch block executes. The catch block deals with any errors that occur in the try block. • The finally block executes In many cases, the finally block is not required, and is, therefore, omitted. In the coming examples, we’ll omit the finally block. Later in the section “The finally Block,” we’ll study a finally block example. When the catch block is executed, it receives the expression of the throw statement as a parameter. In the catch block, we can use that expression to help diagnose the error thrown by the try block. Metaphorically, the code that encounters a problem throws an exception (passes an Error object) to the catch block, which receives it as a parameter (catches it). 244 | Chapter 13: Exceptions and Error Handling We’ll find out what happens if an error is never caught later in the sec- tion “Uncaught Exceptions,” later in this chapter. Here’s an example try/catch/finally statement. try { somePet.setName("James"); // If we get this far, no exception occurred; proceed as planned. trace("Pet name set successfully."); } catch (e:Error) { // ERROR! Invalid data. Display a warning. trace("An error occurred: " + e.message); } When we invoke pet.setName( ) within the preceding try block, if setName( )’s throw statement doesn’t execute (if no error occurs), then the subsequent statements in the try block execute normally, and the program skips the catch block entirely. But if setName( ) throws an exception, the program immediately skips to and executes the catch block. Within the catch block, the value of the parameter e is the Error object from the throw statement in setName( ). In the preceding example, the code in the catch block simply displays that Error object’s message variable during debugging. But in a more sophisticated application, the catch block might attempt to recover from the error, perhaps by displaying a dialog boxrequesting that the user supply a valid name. Handling Multiple Types of Exceptions The exception example from the preceding section was simplistic. What happens if our method generates more than one kind of error? Are they all sent to the same catch block? Well, that’s up to the developer; they certainly could be, but it’s more typical and better practice for different kinds of errors to be handled by separate catch blocks. Let’s examine why. Suppose we want a finer-grained set of error messages in our setName( ) method: one for general invalid data, one for a name that’s too short, and one for a name that’s too long. The body of our revised setName( ) method might look like this: if (newName.indexOf(" ") == 0) { // Names can't start with a space... throw new Error("Invalid pet name specified."); } else if (newName == "") { throw new Error("Pet name too short."); } else if (newName.length > VirtualPet.maxNameLength) { throw new Error("Pet name too long."); } Handling Multiple Types of Exceptions | 245 To handle the three possible error messages in our new setName( ) message, we might be tempted to code our try/catch/finally statement as follows: try { somePet.setName("somePetName"); // If we get this far, no exception occurred; proceed as planned. trace("Pet name set successfully."); } catch (e:Error) { switch (e.message) { case "Invalid pet name specified.": trace("An error occurred: " + e.message); trace("Please specify a valid name."); break; case "Pet name too short.": trace("An error occurred: " + e.message); trace("Please specify a longer name."); break; case "Pet name too long.": trace("An error occurred: " + e.message); trace("Please specify a shorter name."); break; } } Admittedly, that code does work, but it’s fraught with problems. First, and most serious, the errors are distinguished from one another only by the text in a string that is hidden within the VirtualPet class. Each time we want to check what kind of errors might occur in setName( ), we have to look inside the VirtualPet class and find the error message strings. Using message strings for error identification across multiple methods and classes is highly prone to human error and makes our code difficult to maintain. Second, the switch statement itself is hard to read. We’re not much farther ahead than we would be if we had used, say, numeric error codes instead of formal exceptions. Fortunately, there’s a formal (and elegant) way to handle multiple exception types. Each try block can have any number of supporting catch blocks. When an exception is thrown in a try block that has multiple catch blocks, ActionScript executes the catch block whose parameter’s datatype matches the datatype of the value originally thrown. Here’s the general syntax of a try statement with multiple catch blocks: try { // Code that might generate an exception. } catch (e:ErrorType1) { // Error-handling code for ErrorType1. } catch (e:ErrorType2) { // Error-handling code for ErrorType2. } catch (e:ErrorTypen) { // Error-handling code for ErrorTypen. } 246 | Chapter 13: Exceptions and Error Handling If a throw statement in the preceding try block were to throw an expression of type ErrorType1, then the first catch block would be executed. For example, the follow- ing code causes the first catch block to execute: throw new ErrorType1( ); If a throw statement were to pass an expression of type ErrorType2, then the second catch clause would be executed, and so on. As we learned earlier, in ActionScript the throw statement expression can belong to any datatype. However, remember that, as a best practice, most programs throw instances of the Error class or one of its sub- classes only. If we want to throw multiple kinds of exceptions in an application, we define an Error subclass for each kind of exception. It is up to you as the developer to decide what level of granularity you require (i.e., to what degree you need to differentiate among different error conditions). Determining Exception Type Granularity Should you define an Error subclass for each and every error condition? Typically, no, you won’t need that level of granularity because in many cases multiple error conditions can be treated in the same way. If you don’t need to differentiate among multiple error conditions, you can group them together under a single custom Error subclass. For example, you might define a single Error subclass named InvalidInputException to handle a wide range of input problems. That said, you should define a separate Error subclass for each error condition that you want to distinguish from other possible conditions. To help you understand when you should create a new subclass for a given error condition and to demon- strate how to group multiple conditions into a single subclass, let’s return to the setName( ) method. Earlier we generated three exceptions from the setName( ) method. All three excep- tions used the generic Error class. Here’s the code again: if (newName.indexOf(" ") == 0) { // Names can't start with a space... throw new Error("Invalid pet name specified."); } else if (newName == "") { throw new Error("Pet name too short."); } else if (newName.length > VirtualPet.maxNameLength) { throw new Error("Pet name too long."); } In the preceding code, to differentiate VirtualPet exceptions from all other excep- tions in our application, we used the Error class’s message variable, which, as we just learned, made our exceptions awkward to use and prone to human error. A better way to set VirtualPet-related data errors apart from other errors in our application is to define a custom Error subclass, VirtualPetNameException, as follows: Handling Multiple Types of Exceptions | 247 // Code in VirtualPetNameException.as: package zoo { public class VirtualPetNameException extends Error { public function VirtualPetNameException ( ) { // Pass an error message to the Error constructor, to be // assigned to this object's message variable super("Invalid pet name specified."); } } } With our VirtualPetNameException class in place, our setName( ) method can throw its very own type of error, as follows: public function setName (newName:String):void { if (newName.indexOf(" ") == 0) { throw new VirtualPetNameException( ); } else if (newName == "") { throw new VirtualPetNameException( ); } else if (newName.length > VirtualPet.maxNameLength) { throw new VirtualPetNameException( ); } petName = newName; } Notice that the preceding method definition throws the same error type (VirtualPetNameException) for all three VirtualPet-related error conditions. As devel- opers of the VirtualPet class, we now face the cruxof the error-granularity issue. We must decide not only how distinguishable we want VirtualPet error messages to be from other application errors, but also how distinguishable we want those errors to be from one another. We have the following options: Option 1: Use a single VirtualPet error class. In this option, we leave the preceding setName( ) method definition as it is. As we’ll see shortly, this option lets us distinguish VirtualPet errors from other generic errors in the program, but it does not help us distinguish internally among the three varieties of VirtualPet-related errors (invalid data, too short a pet name, and too long a pet name). Option 2: Simplify code, but still use a single VirtualPet error class. In this option, we modify the setName( ) method so it checks for all three error conditions using a single if statement. This option is the same as the previous option, but uses cleaner code. Option 3: Use debugging messages to distinguish among errors. In this option, we add configurable debugging messages to the VirtualPetNameException class. This option adds slightly more granularity than the previous two options but only for the sake of the developer and only during debugging. 248 | Chapter 13: Exceptions and Error Handling Option 4: Create a custom exception class for each error condition. In this option, we create two custom VirtualPetNameException subclasses, VirtualPetInsufficientDataException and VirtualPetExcessDataException. This option provides the most granularity; it lets a program respond independently to the three varieties of VirtualPet-related errors using formal branching logic. Let’s consider the preceding options in turn. Options 1 and 2: Using a single custom-exception type Our first option is to accept the preceding setName( ) definition, which throws the same error type (VirtualPetNameException) for all three VirtualPet-related error con- ditions. Because the method uses VirtualPetNameException and not Error to throw exceptions, VirtualPet exceptions are already distinguishable from other generic exceptions. Users of the setName( ) method can use code such as the following to discriminate between VirtualPet-related errors and other generic errors: try { // This call to setName( ) will generate a VirtualPetNameException. somePet.setName(""); // Other statements in this try block might generate other generic errors. // For demonstration purposes, we'll throw a generic error directly. throw new Error("A generic error."); } catch (e:VirtualPetNameException) { // Handle VirtualPet name errors here. trace("An error occurred: " + e.message); trace("Please specify a valid name."); } catch (e:Error) { // Handle all other errors here. trace("An error occurred: " + e.message); } For many applications, the level of error granularity provided by VirtualPetNameException is enough. In such a case, we should at least rewrite the setName( ) method so that it doesn’t contain redundant code (throwing the VirtualPetNameException three times). Here’s the rewritten code (which was Option 2 in our earlier list): public function setName (newName:String):void { if (newName.indexOf(" ") == 0 || newName == "" || newName.length > VirtualPet.maxNameLength) { throw new VirtualPetNameException( ); } petName = newName; } Handling Multiple Types of Exceptions | 249 Rewriting code to improve its structure without changing its behavior is known as refactoring. Option 3: Using configurable debugging messages Option 3 adds configurable debugging messages to the VirtualPetNameException class. Options 1 and 2 let us distinguish a VirtualPet exception from other excep- tions in the application but didn’t help us distinguish a “too long” exception from a “too short” exception. If you feel that it’s difficult to debug a VirtualPet name prob- lem without knowing whether a VirtualPet object’s name is too big or too small, you can adjust the VirtualPetNameException class so that it accepts an optional descrip- tion (the equivalent of a proverbial “note to self”). Here’s the adjusted VirtualPetNameException class: package zoo { public class VirtualPetNameException extends Error { // Provide a constructor that allows a custom message to be supplied, // but uses the default error message when no custom message is supplied public function VirtualPetNameException ( message:String = "Invalid pet name specified.") { super(message); } } } To make use of our adjusted VirtualPetNameException class in setName( ), we revert to our setName( ) code used in Option 1 and add debugging error messages, as follows: public function setName (newName:String):void { if (newName.indexOf(" ") == 0) { // The default error message is fine in this case, // so don't bother specifying a custom error message. throw new VirtualPetNameException( ); } else if (newName == "") { // Here's the custom "too short" error message. throw new VirtualPetNameException("Pet name too short."); } else if (newName.length > VirtualPet.maxNameLength) { // Here's the custom "too long" error message. throw new VirtualPetNameException("Pet name too long."); } petName = newName; } Now that setName( ) supplies custom error messages, we’ll have an easier time debugging a VirtualPet problem because we’ll know more information when an error occurs. Our use of the setName( ) method has not changed, but we’re better informed when something goes wrong, as shown next: 250 | Chapter 13: Exceptions and Error Handling try { // This call to setName( ) will generate a VirtualPetNameException. somePet.setName(""); } catch (e:VirtualPetNameException) { // Handle VirtualPet name errors here. // In this case, the helpful debugging output is: // An error occurred: Pet name too short. trace("An error occurred: " + e.message); } catch (e:Error) { // Handle all other errors here. trace("An error occurred: " + e.message); } Option 4: Multiple custom VirtualPetNameException subclasses Option 3 added configurable debugging messages to the VirtualPetNameException class. It helped us investigate a problem in our code during development, but it doesn’t help the program take independent action to recover from individual VirtualPet errors. To allow the program to execute independent code branches based on the type of VirtualPet error thrown, we need custom VirtualPetNameException subclasses, which is Option 4. If you want a program to differentiate among error conditions, imple- ment a separate Error subclass for each one. Don’t rely on the message variable alone to implement program branching logic. If your custom Error subclass defines a constructor that accepts an error message, you should use that message for debugging only, not for branching logic. To independently differentiate among the VirtualPet class’s three error conditions, we’ll cre- ate three Error subclasses: VirtualPetNameException, VirtualPetInsufficientDataException, and VirtualPetExcessDataException. The VirtualPetNameException class extends Error directly. The VirtualPetInsufficientDataException and VirtualPetExcessDataException classes both extend VirtualPetNameException because we want to differentiate these specific error types from a more general invalid data exception. Here’s the source code for our three VirtualPetError subclasses: // Code in VirtualPetNameException.as: package zoo { public class VirtualPetNameException extends Error { public function VirtualPetNameException ( message:String = "Invalid pet name specified.") { super(message); } } } // Code in VirtualPetInsufficientDataException.as: package zoo { public class VirtualPetInsufficientDataException extends VirtualPetNameException { Handling Multiple Types of Exceptions | 251 public function VirtualPetInsufficientDataException ( ) { super("Pet name too short."); } } } // Code in VirtualPetExcessDataException.as: package zoo { public class VirtualPetExcessDataException extends VirtualPetNameException { public function VirtualPetExcessDataException ( ) { super("Pet name too long."); } } } Each class specifies the value of its message variable directly and does not allow it to be customized on a per-use basis. When catching any of the preceding VirtualPet exceptions, our program will use the exception’s datatype (not the message variable) to distinguish between the three kinds of exceptions. Now that we have three exception types, let’s update our setName( ) method to throw those types. Here’s the code: public function setName (newName:String):void { if (newName.indexOf(" ") == 0) { throw new VirtualPetNameException( ); } else if (newName == "") { throw new VirtualPetInsufficientDataException( ); } else if (newName.length > VirtualPet.maxNameLength) { throw new VirtualPetExcessDataException( ); } petName = newName; } Notice that we do not pass any error description to the various VirtualPet exception constructors. Once again, the description of each exception is set by each custom Error subclass using its message variable. With each VirtualPet exception represented by its own class, the errors that can be generated by the setName( ) method are well-known to programmers working with VirtualPet instances. The exception types are visible outside the VirtualPet class, exposed appropriately to programmers working on the application. Just by glancing at the application class hierarchy, the programmer can determine the exceptions that relate to the VirtualPet class. Furthermore, if the programmer mistakenly uses the wrong name for an exception, the compiler generates a datatype error. Now let’s see how to add branching logic to our code based on the types of excep- tions that can be generated by setName( ). Pay close attention to the datatype of each catch block parameter and the placement of each catch block. 252 | Chapter 13: Exceptions and Error Handling try { b.setName("somePetName"); } catch (e:VirtualPetExcessDataException) { // Handle "too long" case. trace("An error occurred: " + e.message); trace("Please specify a shorter name."); } catch (e:VirtualPetInsufficientDataException) { // Handle "too short" case. trace("An error occurred: " + e.message); trace("Please specify a longer name."); } catch (e:VirtualPetNameException) { // Handle general name errors. trace("An error occurred: " + e.message); trace("Please specify a valid name."); } In the preceding code, if the setName( ) method generates a VirtualPetExcessDataException, the first catch block executes. If setName( ) gener- ates a VirtualPetInsufficientDataException, the second catch block executes. And if setName( ) generates a VirtualPetNameException, the third catch block executes. Notice that the error datatypes in the catch blocks progress from specific to general. When an exception is thrown, the catch block executed is the first one that matches the datatype of the exception. Hence, if we changed the datatype of the first catch block parameter to VirtualPetNameException, the first catch block would execute for all three kinds of exceptions! (Remember, VirtualPetNameException is the superclass of both VirtualPetInsufficientDataException and VirtualPetExcessDataException, so they are considered matches for the VirtualPetNameException datatype.) In fact, we could prevent all of the catch blocks from executing simply by adding a new first catch block with a parameter datatype of Error: try { b.setName("somePetName"); } catch (e:Error) { // Handle all errors. No other catch blocks will ever execute. trace("An error occurred:" + e.message); trace("The first catch block handled the error."); } catch (e:VirtualPetExcessDataException) { // Handle "too long" case. trace("An error occurred: " + e.message); trace("Please specify a shorter name."); } catch (e:VirtualPetInsufficientDataException) { // Handle "too short" case. trace("An error occurred: " + e.message); trace("Please specify a longer name."); } catch (e:VirtualPetNameException) { // Handle general name errors. trace("An error occurred: " + e.message); trace("Please specify a valid name."); } Exception Bubbling | 253 Obviously, the addition of the first catch clause in the preceding code is self-defeating, but it does illustrate the hierarchical nature of exception handling. By placing a very generic catch block at the beginning of the catch list, we can handle all errors in a single location. Conversely, by placing a very generic catch block at the end of the catch list, we can provide a safety net that handles any errors not caught by earlier catch blocks. For example, in the following code, the final catch block executes only if the try block gener- ates an exception that doesn’t belong to the VirtualPetExcessDataException, VirtualPetInsufficientDataException, or VirtualPetNameException datatypes: try { b.setName("somePetName"); } catch (e:VirtualPetExcessDataException) { // Handle overflow. trace("An error occurred: " + e.message); trace("Please specify a smaller value."); } catch (e:VirtualPetInsufficientDataException) { // Handle under zero. trace("An error occurred: " + e.message); trace("Please specify a larger value."); } catch (e:VirtualPetNameException) { // Handle general dimension errors. trace("An error occurred: " + e.message); trace("Please specify a valid dimension."); } catch (e:Error) { // Handle any errors that don't qualify as VirtualPetNameException errors. } Remember, error granularity is a choice. In Option 4 we created a custom Error sub- class for each variety of exception generated by the VirtualPet class. This approach gives our program the greatest ability to respond independently to different types of errors. But such flexibility is not necessarily required in many situations. Let the needs of your program’s logic dictate how granular you make your errors. Exception Bubbling In ActionScript, an exception can be thrown anywhere in a program, even on a frame in a timeline script! Given that an exception can be thrown anywhere, how does ActionScript find the corresponding catch block to handle it? And what if no catch block exists? These mysteries are resolved through the magic of exception bubbling. Let’s follow along a bubbly ride with ActionScript as it encounters a throw state- ment in a program. During the following dramatization, ActionScript’s musings are shown in code comments. When a throw statement executes, ActionScript immediately stops normal program flow and looks for an enclosing try block. For example, here’s a throw statement: // ActionScript: Hmm. A throw statement. // Is there an enclosing try block for it? throw new Error("Something went wrong"); 254 | Chapter 13: Exceptions and Error Handling If the throw statement is enclosed in a try block, ActionScript next tries to find a catch block whose parameter’s datatype matches the datatype of the value thrown (in this case, Error): // ActionScript: Great, I found a try block. // Is there a matching catch block? try { throw new Error("Something went wrong"); } If a matching catch block is found, ActionScript transfers program control to that block: try { throw new Error("Something went wrong"); // ActionScript: Found a catch block whose parameter datatype is Error! // The hunt's over. I'll execute this catch block now... } catch (e:Error) { // Handle problems... } But if a matching catch block cannot be found or if the throw statement did not appear within a try block in the first place, then ActionScript checks whether the throw statement occurred within a method or function. If the throw statement occurred in a method or function, ActionScript searches for a try block around the code that invoked the method or function. The following code demonstrates how ActionScript reacts when, within a method, it encounters a throw statement that has no enclosing try block: public function doSomething ( ):void { // ActionScript: Hmm. No try block here. // I'll check who called this method. throw new Error("Something went wrong"); } If the code that invoked the method or function is enclosed in a try block, Action- Script looks for a matching catch block there and, if it finds a match, executes it. The following code demonstrates an exception thrown out of a method and caught where the method is invoked (i.e., one level up the call stack): public class ProblemClass { public function doSomething ( ):void { // ActionScript: Hmm. No try block here. // I'll check who called this method. throw new Error("Something went wrong"); } } public class ErrorDemo extends Sprite { public function ErrorDemo ( ) { // ActionScript: Aha, here's who called doSomething( ). And here's // an enclosing try block with a catch block whose // parameter datatype is Error! My work's done. catch Exception Bubbling | 255 // block, please execute now... try { var problemObject:ProblemClass = new ProblemClass( ); problemObject.doSomething( ); } catch (e:Error) { // Handle problems... trace("Exception caught in ErrorDemo, thrown by doSomething( )."); } } } The call stack is the list of functions and methods currently being exe- cuted by ActionScript at any given point in a program. The list includes the functions and methods in the reverse order from which they were called, from top to bottom. When a function is immediately below another function in the call stack, then the lower function was invoked by the higher. The lowest function in the call stack is the function currently executing. In FlexBuilder and the Flash authoring tool, you can use the debug- ger to view the call stack for the current program, as described in Adobe’s documentation. In the preceding code, an exception thrown by a method was caught by a try/catch block enclosing the method call statement. However, if no try block is found around the function or method caller, ActionScript searches up the entire call stack for a try block with a matching catch block. The following code shows a method throwing an error that is caught two levels up the call stack: public class ProblemClass { public function doSomething ( ):void { // ActionScript: Hmm. No try block here. // I'll check who called this method. throw new Error("Something went wrong"); } } public class NormalClass { public function NormalClass ( ) { // ActionScript: Aha, here's who called doSomething( ). But still // no try block here. I'll check who called this method. var problemObject:ProblemClass = new ProblemClass( ); problemObject.doSomething( ); } } public class ErrorDemo extends Sprite { public function ErrorDemo ( ) { // ActionScript: Aha! Found a try block that has a catch block whose // parameter's datatype is Error! My work's done. // catch block, please execute now... try { 256 | Chapter 13: Exceptions and Error Handling var normalObject:NormalClass = new NormalClass( ); } catch (e:Error) { // Handle problems... trace("Exception caught in ErrorDemo."); } } } Notice that ActionScript finds the try/catch block despite the fact that it surrounds not the error-throwing code, nor the caller of the error-throwing method, but the caller of the method that called the error-throwing method! The following code shows the preceding bubbling example in the context of our vir- tual pet program. In the following code listing, for brevity, only the pet-naming code is shown. Comments in the code describe how the exception bubbles. package { import flash.display.Sprite; import zoo.*; public class VirtualZoo extends Sprite { private var pet:VirtualPet; public function VirtualZoo ( ) { try { // This code attempts to give a pet a name that is too long. // As a result, the setName( ) method throws an error. // However, the exception is not caught in the VirtualPet // constructor (where setName( ) is called). Instead, the exception // is caught here, where the VirtualPet constructor was // called (i.e., two levels up the call stack). pet = new VirtualPet("Bartholomew McGillicuddy"); } catch (e:Error) { trace("An error occurred: " + e.message); // If attempting to create a VirtualPet object causes an exception, // then the object won't be created. Hence, we create a new // VirtualPet object here with a known-to-be-valid name. pet = new VirtualPet("Unnamed Pet"); } } } } package zoo { public class VirtualPet { public function VirtualPet (name:String):void { // Even though the setName( ) method is called here, exceptions thrown // by setName( ) are not handled here. They are handled up the call // stack, by the code that created this VirtualPet object. setName(name); } public function setName (newName:String):void { // Exceptions thrown in this method are not handled here. They are Exception Bubbling | 257 // handled two-levels up the call stack, by the code that created // this VirtualPet object. if (newName.indexOf(" ") == 0) { throw new VirtualPetNameException( ); } else if (newName == "") { throw new VirtualPetInsufficientDataException( ); } else if (newName.length > VirtualPet.maxNameLength) { throw new VirtualPetExcessDataException( ); } petName = newName; } } } Uncaught Exceptions We’ve seen a number of scenarios in which we’ve caught various errors. But what happens if ActionScript never finds a catch block that can handle a thrown excep- tion? If no eligible catch block is found anywhere in the call stack, then ActionScript aborts execution of all code currently remaining in the call stack. In addition, if the program is running in the debugger version of the Flash runtime, the error is reported via either a dialog box, the Output panel (Flash authoring tool), or the Con- sole panel (Flex Builder). Execution of the program then resumes normally. The following code demonstrates a method that throws an error that is never caught: public class ProblemClass { public function doSomething ( ):void { // ActionScript: Hmm. No try block here. // I'll check who called this method. throw new Error("Something went wrong"); } } public class ErrorDemo extends Sprite { public function ErrorDemo ( ) { // ActionScript: Aha, here's who called doSomething( ). But still // no try block here. Hmm. I searched all the way to the top, and found // no try block. If this Flash runtime is a debugger version, I'll // report the problem. Maybe the programmer will know what to do. var problemObject:ProblemClass = new ProblemClass( ); problemObject.doSomething( ); } } As we’ve just seen, because exceptions bubble up the call stack, it’s not necessary for a method to catch its own exceptions. And it’s not even necessary for the caller of a method to catch its exceptions. The exception can legally be caught at any level in the call stack. Any method can delegate exception handling to the code that calls it. That said, it’s bad form and harmful to a program to throw an exception and then never 258 | Chapter 13: Exceptions and Error Handling catch it. You should always catch exceptions or, having encountered an uncaught exception, revise your code so that the exception isn’t thrown in the first place. The finally Block So far, we’ve discussed only the try and catch blocks in the try/catch/finally state- ment. As we’ve seen, a try block contains code that might throw an exception, and a catch block contains code that executes in response to a thrown exception. The finally block, by comparison, contains code that always executes, whether or not code in the try block throws an exception. The finally block is placed once (and only once) as the last block in a try/catch/finally statement. For example: try { // substatements } catch (e:ErrorType1) { // Handle ErrorType1 exceptions. } catch (e:ErrorTypen) { // Handle ErrorTypen exceptions. } finally { // This code always executes, no matter how the try block exits. } Misplacing the finally block causes a compile-time error. In the preceding code, the finally block executes in one of these four circumstances: • Immediately after the try block completes without errors • Immediately after a catch block handles an exception generated in the try block • Immediately before an uncaught exception bubbles up • Immediately before a return, continue,orbreak statement transfers control out of the try or catch blocks The finally block of a try/catch/finally statement typically contains cleanup code that must execute whether or not an exception occurs in the corresponding try block. For example, suppose we’re creating a space shooter game, and we define a class, SpaceShip, to represent spaceships in the game. The SpaceShip class has a method, attackEnemy( ), which performs the following tasks: • Sets the spaceship’s current target • Fires on that target • Clears the current target (by setting the SpaceShip object’s currentTarget variable to null) In our hypothetical application, we’ll assume that the first two of the preceding tasks might generate an exception. Further, we’ll assume that the attackEnemy( ) method doesn’t handle those exceptions itself; instead, it passes them up to the calling The finally Block | 259 method. Regardless of whether an exception is generated, the attackEnemy( ) method must set the currentTarget variable to null. Here’s what the attackEnemy( ) method would look like if we coded it with a catch statement (i.e., without using finally): public function attackEnemy (enemy:SpaceShip):void { try { setCurrentTarget(enemy); fireOnCurrentTarget( ); } catch (e:Error) { // Clear the current target if an exception occurs. setCurrentTarget(null); // Pass the exception up to the calling method. throw e; } // Clear the current target if no exception occurs. setCurrentTarget(null); } Notice that we must duplicate the statement, setCurrentTarget(null). We place it both in the catch block and after the try/catch statement, guaranteeing that it will run whether or not there’s an exception in the try block. However, duplicating the state- ment is error prone. In the preceding method, a programmer could have easily for- gotten to clear the current target after the try/catch block. If we change our strategy by clearing the current target in a finally block, we remove the redundancy in the preceding code: public function attackEnemy (enemy:SpaceShip):void { try { setCurrentTarget(enemy); fireOnCurrentTarget( ); } finally { setCurrentTarget(null); } } In the revised version, the finally block clears the current target whether there’s an exception or not. Because both situations are handled, we no longer have any need for a catch block; we can simply let the exception bubble up to the calling method automatically. You might be wondering why we need the finally block at all. That is, why not just use the following code? // This code might look decent, but there's a problem. Can you spot it? public function attackEnemy (enemy:SpaceShip):void { setCurrentTarget(enemy); fireOnCurrentTarget( ); setCurrentTarget(null); } 260 | Chapter 13: Exceptions and Error Handling Remember that when an exception is thrown, program control is transferred to the nearest suitable catch block in the call stack. Hence, if fireOnCurrentTarget( ) throws an exception, control transfers out of attackEnemy( ), never to return, and setCurrentTarget(null) would never execute. By using a finally block, we guarantee that setCurrentTarget(null) executes before the exception bubbles up. The attackEnemy( ) method example reflects the most common use of finally in mul- tithreaded languages like Java, where a program can have multiple sections of code executing simultaneously. In Java, the following general structure is commonplace; it guards against the possibility that an object busy with a task might be inappropri- ately altered by another object during the execution of that task: // Set a state indicating this object's current task. // External objects should check this object's state // before accessing or manipulating this object. doingSomething = true; try { // Perform the task. doSomething( ); } finally { // Unset the "in-task" state (whether or not // the task generated an exception). doingSomething = false; } In ActionScript, the preceding state-management code is effectively unnecessary because the language is single-threaded, so no external object will ever attempt to alter the state of an object while it is busy executing a method. Therefore, finally is used much more rarely in ActionScript than it is in multithreaded languages. How- ever, it can still be used for organizational purposes, to contain code that performs cleanup duties after other code has executed. Nested Exceptions So far we’ve used only single-level try/catch/finally statements, but exception-han- dling logic can also be nested. A try/catch/finally statement can appear inside the try, catch,orfinally block of another try/catch/finally statement. This hierarchical nest- ing allows any block in a try/catch/finally statement to execute code that might, itself, throw an error. For example, suppose we were writing a multiuser, web-based message board sys- tem. We define the following classes: BulletinBoard, the application’s main class; GUIManager, which manages the user interface; and User, which represents a user on the board. We give BulletinBoard a method, populateUserList( ), which displays the list of current active users. The populateUserList( ) method splits its work into two stages: first it retrieves an instance of a List class from the application’s GUIManager instance. The List class represents an onscreen user list. Then Nested Exceptions | 261 populateUserList( ) populates that List instance with users from a supplied array of User instances. These two stages can both potentially generate an exception, so a nested try/catch/finally structure is used in the populateUserList( ) method. Let’s take a closer look at this nested structure. During the first stage of populateUserList( ), if the List instance isn’t available, a UserListNotFound exception is thrown by the GUIManager. The UserListNotFound exception is caught by the outer try/catch/finally statement. If, on the other hand, the List instance is available, the populateUserList( ) method proceeds with stage two, during which a loop populates the List instance with users from the supplied array. For each iteration through the loop, if the current user’s ID cannot be found, the User.getID( ) method throws a UserIdNotSet exception. The UserIdNotSet exception is caught by the nested try/catch/finally statement. Here’s the code: public function populateUserList (users:Array):void { try { // Start stage 1...get the List instance. // If getUserList( ) throws an exception, the outer catch executes. var ulist:List = getGUIManager().getUserList( ); // Start stage 2...populate the List. for (var i:Number = 0; i < users.length; i++) { try { var thisUser:User = User(users[i]); // If getID( ) throws an exception, the nested catch executes. // If not, the user is added to the List instance via addItem( ). ulist.addItem(thisUser.getName(), thisUser.getID( )); } catch (e:UserIdNotSet) { trace(e.message); continue; // Skip this user. } } } catch (e:UserListNotFound) { trace(e.message); } } Now that we’ve had a look at a specific nested exception example, let’s consider how nested exceptions are handled in general. If an exception occurs in a try block that is nested within another try block, and the inner try block has a catch block that can handle the exception, then the inner catch block is executed, and the program resumes at the end of the inner try/catch/finally statement. try { try { // Exception occurs here. throw new Error("Test error"); } catch (e:Error) { 262 | Chapter 13: Exceptions and Error Handling // Exception is handled here. trace(e.message); // Displays: Test error } // The program resumes here. } catch (e:Error) { // Handle exceptions generated by the outer try block. } If, on the other hand, an exception occurs in a try block that is nested within another try block, but the inner try block does not have a catch block that can handle the exception, then the exception bubbles up to the outer try/catch/finally statement (and, if necessary, up the call stack) until a suitable catch block is found, or the exception is not caught. If the exception is caught somewhere in the call stack, the program resumes at the end of the try/catch/finally statement that handled the excep- tion. Note that in the following code example (and subsequent examples), the hypo- thetical error datatype SomeSpecificError is a placeholder used to force the thrown exception to not be caught. In order to test the code example in your own code, you’d have to create a subclass of Error called SomeSpecificError. try { try { // Exception occurs here. throw new Error("Test error"); } catch (e:SomeSpecificError) { // Exception is not handled here. trace(e.message); // Never executes because the types don't match. } } catch (e:Error) { // Exception is handled here. trace(e.message); // Displays: Test error } // The program resumes here, immediately after the outer catch block // has handled the exception. If an exception occurs in a try block that is nested within a catch block, and the inner try block does not have a catch block that can handle the exception, then the search for a matching catch block begins outside the outer try/catch/finally statement: try { // Outer exception occurs here. throw new Error("Test error 1"); } catch (e:Error) { // Outer exception handled here. trace(e.message); // Displays: Test error 1 try { // Inner exception occurs here. throw new Error("Test error 2"); } catch (e:SomeSpecificError) { // Inner exception is not handled here. trace(e.message); // Never executes because the types don't match. } } Nested Exceptions | 263 // The search for a matching catch block for the // inner exception starts here. Last, if an exception occurs in a try block that is nested within a finally block, but a prior exception is already in the process of bubbling up the call stack, then the new exception is handled before the prior exception continues to bubble up. // This method throws an exception in a finally block. public function throwTwoExceptions ( ):void { try { // Outer exception occurs here. Because there is no catch block for this // try block, the outer exception starts to bubble up, // out of this method. throw new Error("Test error 1"); } finally { try { // Inner exception occurs here. The inner exception is // handled before the outer exception actually bubbles up. throw new Error("Test error 2"); } catch (e:Error) { // Inner exception is handled here. trace("Internal catch: " + e.message); } } } // Elsewhere, within another method that calls the preceding method. try { throwTwoExceptions( ); } catch (e:Error) { // The outer exception, which has bubbled up from throwTwoExceptions( ), // is handled here. trace("External catch: " + e.message); } // Output (notice that the inner exception is caught first): Internal catch: Test error 2 External catch: Test error 1 If, in the preceding example, the exception thrown in the finally block had never been caught, then ActionScript would have reported the error during debugging, and aborted all other code in the call stack. As a result, the original, outer exception would have been discarded along with all code in the call stack. The following code demonstrates the preceding principle. It throws an uncaught exception from a finally statement. As a result, the exception thrown by the outer try block is discarded. try { // Outer exception occurs here. throw new Error("Test error 1"); } finally { try { // Inner exception occurs here. throw new Error("Test error 2"); 264 | Chapter 13: Exceptions and Error Handling } catch (e:SomeSpecificError) { // Inner exception is not handled here. trace("internal catch: " + e.message); // Never executes because // the types don't match. } } // The search for a matching catch block for the inner exception starts // here. If no match is ever found, then the inner exception is reported // during debugging, and the bubbling of the outer exception is aborted. The preceding code demonstrates the effect of an uncaught exception in one sce- nario, but once again, it’s not appropriate to allow an exception to go uncaught. In the preceding case, we should either catch the exception or revise our code so that the exception isn’t thrown in the first place. Control-Flow Changes in try/catch/finally As we’ve seen throughout this chapter, the throw statement changes the flow of a program. When ActionScript encounters a throw statement, it immediately stops what it’s doing and transfers program control to eligible catch and finally blocks. However, it is also quite legal for those catch and finally blocks to change program flow again via return (in the case of a method or function) or break or continue (in the case of a loop). Furthermore, a return, break,orcontinue statement can also appear in a try block. To learn the rules of flow changes in the try/catch/finally statement, let’s look at how the return statement affects program flow in a try, catch, and finally block. The fol- lowing code examples contain a function, changeFlow( ), which demonstrates a con- trol flow in various hypothetical situations. Example 13-1 shows a return statement in a try block, placed before an error is thrown. In this case, the method returns normally, and no error is ever thrown or handled. However, before the method returns, the finally block is executed. Note that you’re unlikely to see code exactly like Example 13-1 in the real world. In most applied cases, the return statement would occur in a conditional statement and exe- cute in response to some specific condition in the program. Example 13-1. Using return in try, before throw public function changeFlow ( ):void { try { return; throw new Error("Test error."); } catch (e:Error) { trace("Caught: " + e.message); } finally { trace("Finally executed."); } trace("Last line of method."); Control-Flow Changes in try/catch/finally | 265 Example 13-2 shows a return statement in a try block, placed after an error is thrown. In this case, the return statement is never executed because an error is thrown before it is reached. Once the error is caught and the try/catch/finally com- pletes, execution resumes after the try/catch/finally statement, and the method exits at the end of the method body. Again, Example 13-2 demonstrates a principle but is atypical of real-world code, which would normally throw the error based on some condition. Example 13-3 shows a return statement in a catch block. In this case, the return state- ment executes when the work of error handling is done, and the code after the try/ catch/finally statement never executes. However, as usual, before the method returns, the finally block is executed. Unlike Examples 13-1 and 13-2, this code is typical of a real-world scenario in which a method is aborted due to the occurrence of an error. } // Output when changeFlow( ) is invoked: Finally executed. Example 13-2. Using return in try, after throw public function changeFlow ( ):void { try { throw new Error("Test error."); return; } catch (e:Error) { trace("Caught: " + e.message); } finally { trace("Finally executed."); } trace("Last line of method."); } // Output when changeFlow( ) is invoked: Caught: Test error. Finally executed. Last line of method. Example 13-3. Using return in catch public function changeFlow ( ):void { try { throw new Error("Test error."); } catch (e:Error) { trace("Caught: " + e.message); return; } finally { trace("Finally executed."); } trace("Last line of function."); Example 13-1. Using return in try, before throw (continued) 266 | Chapter 13: Exceptions and Error Handling Due to a known bug, the code in Examples 13-2 and 13-3 causes a stack underflow in Flash Player 9. Adobe expects to fix the problem in a future version of Flash Player. Example 13-4 shows a return statement in a finally block. In this case, the return statement executes when the finally block executes (as we learned earlier, a finally block executes when its corresponding try block completes in one of the following ways: without errors; with an error that was caught; with an error that was not caught; or due to a return, break,orcontinue statement). Notice that the return state- ment in Example 13-4 prevents any code in the method beyond the try/catch/finally statement from executing. You might use a similar technique to quit out of a method after invoking a block of code, whether or not that code throws an exception. In such a case, you’d typically surround the entire try/catch/finally block in a condi- tional statement (otherwise the remainder of the method would never execute!). If a return statement occurs in a finally block after a return has already been issued in the corresponding try block, then the return in the finally block replaces the return already in progress. } // Output when changeFlow( ) is invoked: Caught: Test error. Finally executed. Example 13-4. Using return in finally public function changeFlow ( ):void { try { throw new Error("Test error."); } catch (e:Error) { trace("Caught: " + e.message); } finally { trace("Finally executed."); return; } trace("Last line of method."); // Not executed. } // Output when changeFlow( ) is invoked: Caught: Test error. Finally executed. Example 13-3. Using return in catch (continued) Handling a Built-in Exception | 267 Handling a Built-in Exception At the very beginning of this chapter, we learned that we can respond to both built- in and custom errors using the try/catch/finally statement. So far, the errors we’ve handled have all been custom errors. To close this chapter, let’s examine a try/catch/ finally statement that handles a built-in error. Suppose we’re building a chat application in which the user is asked to specify a port number when connecting to the chat server. We assign the specified port number to a variable named userPort. Then, we use the Socket class to attempt to connect to the specified port. In some cases, the connection will fail due to security limitations. To indicate that a security limitation has been breached when a connection attempt is made, the Flash runtime throws a SecurityError. Therefore, when attempting to make a connection, we wrap the connection code in the try block. If the connection fails due to security reasons, we display an error message to the user indicating what went wrong. var socket:Socket = new Socket( ); try { // Attempt to connect to the specified port socket.connect("example.com", userPort); } catch (e:SecurityError) { // Code here displays message to the user } For a list of circumstances that can cause socket connection failures, see the Socket class’s connect( ) method in Adobe’s ActionScript Lan- guage Reference. Error Events for Problem Conditions In the preceding section, we saw how to handle an exception caused by an illegal socket-connection attempt. But not all error conditions in ActionScript result in exceptions. Problems that occur asynchronously (i.e., after some time passes) are reported via error events, not exceptions. For example, if we attempt to load a file, ActionScript must first asynchronously check to see if that file exists. If the file does not exist, ActionScript dispatches an IOErrorEvent.IO_ERROR event. To handle the problem, the code that instigated the load operation must register a listener for the IOErrorEvent.IO_ERROR event. If no listener is registered for that event, then a run- time error occurs. For an error event-handling example, see the examples in the sec- tion “Two More Event Listener Registration Examples” in Chapter 12. 268 | Chapter 13: Exceptions and Error Handling More Gritty Work Ahead Exceptions are not the most glamorous aspect of programming. It’s usually more fun to build something than to diagnose what went wrong with it. Nevertheless, exception handling is an important part of any program’s development. In the com- ing chapter, we’ll study another similarly gritty topic, garbage collection. Garbage collection helps prevent a program from running out of system memory. 269 Chapter 14 CHAPTER 14 Garbage Collection15 Every time a program creates an object, ActionScript stores it in system memory (for example, in RAM). As a program creates hundreds, thousands, or even millions of objects, it slowly occupies more and more memory. To prevent system memory from being fully depleted, ActionScript automatically removes objects from memory when they are no longer needed by the program. The automatic removal of objects from memory is known as garbage collection. Eligibility for Garbage Collection In an ActionScript program, an object becomes eligible for garbage collection as soon as it becomes unreachable. An object is unreachable when it cannot be accessed directly or indirectly through at least one garbage collection root. The most signifi- cant garbage collection roots in ActionScript are as follows: • Package-level variables • Local variables of a currently executing method or function • Static variables • Instance variables of the program’s main class instance • Instance variables of an object on the Flash runtime display list • Variables in the scope chain of a currently executing function or method For example, consider the following version of the VirtualZoo class from our zoo program. Notice that in this version, the VirtualPet object created in the VirtualZoo constructor is not assigned to a variable. package { import flash.display.Sprite; import zoo.*; public class VirtualZoo extends Sprite { public function VirtualZoo ( ) { new VirtualPet("Stan"); 270 | Chapter 14: Garbage Collection } } } In the preceding code, when the VirtualZoo constructor runs, the expression new VirtualPet("Stan") creates a new VirtualPet object. After that object is created, how- ever, it cannot be accessed via any variable. As a result, it is considered unreachable and immediately becomes eligible for garbage collection. Next, consider the following version of the VirtualZoo class. Focus on the construc- tor method body, shown in bold: package { import flash.display.Sprite; import zoo.*; public class VirtualZoo extends Sprite { public function VirtualZoo ( ) { var pet:VirtualPet = new VirtualPet("Stan"); } } } As before, when the preceding VirtualZoo constructor runs, the expression new VirtualPet("Stan") creates a new VirtualPet object. However, this time the VirtualPet object is assigned to a local variable, pet. Throughout the VirtualZoo con- structor, the VirtualPet object can be accessed via that local variable, so it is not eligi- ble for garbage collection. However, as soon as the VirtualZoo constructor finishes running, the variable pet expires, and the VirtualPet object can no longer be accessed via any variable. As a result, it becomes unreachable and eligible for garbage collec- tion. Next, consider the following version of the VirtualZoo class: package { import flash.display.Sprite; import zoo.*; public class VirtualZoo extends Sprite { private var pet:VirtualPet; public function VirtualZoo ( ) { pet = new VirtualPet("Stan"); } } } In the preceding code, when the VirtualZoo constructor runs, the expression new VirtualPet("Stan") again creates a new VirtualPet object. This time, however, the VirtualPet object is assigned to an instance variable of the program’s main class. As such, it is considered reachable, and therefore not eligible for garbage collection. Eligibility for Garbage Collection | 271 Now consider the following version of the VirtualZoo class (again, focus on the bold sections): package { import flash.display.Sprite; import zoo.*; public class VirtualZoo extends Sprite { private var pet:VirtualPet; public function VirtualZoo ( ) { pet = new VirtualPet("Stan"); pet = new VirtualPet("Tim"); } } } As before, line 1 of the preceding constructor creates a VirtualPet object and assigns it to the instance variable, pet. However, line 2 of the preceding constructor method then creates another VirtualPet object and also assigns it to the instance variable, pet. The second assignment replaces pet’s first value (i.e., the pet “Stan”) with a new value (i.e., the pet “Tim”). As a result, the VirtualPet object named “Stan” becomes unreachable, and eligible for garbage collection. Note that we could have also made the VirtualPet object named “Stan” unreachable by assigning null (or any other legal value) to pet, as in: pet = null; Finally, consider the following version of the VirtualZoo class, which defines two instance variables, pet1 and pet2: package { import flash.display.Sprite; import zoo.*; public class VirtualZoo extends Sprite { private var pet1:VirtualPet; private var pet2:VirtualPet; public function VirtualZoo ( ) { pet1 = new VirtualPet("Stan"); pet2 = pet1; pet1 = null; pet2 = null; } } } As before, line 1 of the preceding constructor creates a VirtualPet object; for conve- nience, let’s call it “Stan.” Line 1 also assigns Stan to the instance variable, pet1. Line 2 then assigns that same object to the instance variable, pet2. After line 2 is finished executing, and before line 3 executes, the program can access Stan through both pet1 272 | Chapter 14: Garbage Collection and pet2, so Stan is not eligible for garbage collection. When line 3 executes, pet1’s value is replaced with the value null, so Stan can no longer be accessed through pet1. However, Stan can still be accessed through pet2, so it is still not eligible for garbage collection. Finally, line 4 executes, and pet2’s value is replaced with the value null. As a result, Stan can no longer be accessed through any variable. Poor Stan is unreachable, and officially becomes eligible for garbage collection. There are two special cases in ActionScript where an object that is reachable becomes eligible for garbage collection. For details, see the section “Weak Listener References” in Chapter 12, and “Events and Event Handling” and the Dictionary class in Adobe’s ActionScript Language Reference. Incremental Mark and Sweep In the preceding section, we learned that an object becomes eligible for garbage col- lection (automatic removal from memory) when it is unreachable. But we didn’t learn exactly when unreachable objects are removed from memory. In an ideal world, objects would be removed from memory immediately upon becoming unreachable. However, in practice, removing unreachable objects immediately would be very time consuming and would cause most nontrivial programs to run slowly or become unresponsive. Accordingly, ActionScript does not remove unreach- able objects from memory immediately. Instead, it checks for and removes unreach- able objects only periodically, during garbage collection cycles. ActionScript’s unreachable-object removal strategy is known as incremental mark and sweep garbage collection. Here’s how it works: when the Flash runtime starts, it asks the operating system to set aside, or allocate, an arbitrary amount of memory in which to store the objects of the currently running program. As the program runs, it accumulates objects in memory. At any given time, some of the program’s objects will be reachable, and others might not be reachable. If the program creates enough objects, ActionScript will eventually decide to perform a garbage collection cycle. During the cycle, all objects in memory are audited for “reachability.” All reachable objects are said to be marked for keeping, and all unreachable objects are said to be swept (removed) from memory. However, for a large program, the process of audit- ing objects for reachability can be time-consuming. Accordingly, ActionScript breaks garbage collection cycles into small chunks, or increments, which are interwoven with the program’s execution. ActionScript also uses deferred reference counting to help improve garbage collection performance. For information on deferred reference counting, see “The Memory Management Reference: Beginner’s Guide: Recycling,” at http://www.memorymanagement.org/articles/recycle.html#reference.deferred. ActionScript’s garbage collection cycles are typically triggered when the amount of memory required to store a program’s objects approaches the amount of memory Disposing of Objects Intentionally | 273 currently allocated to the Flash runtime. However, ActionScript makes no guarantee as to when its garbage collection cycles will occur. Furthermore, the programmer cannot force ActionScript to perform a garbage collection cycle. Disposing of Objects Intentionally In ActionScript, there is no direct way to remove an object from system memory. All object-removal happens indirectly, through the automatic garbage collection system. However, while a program cannot remove an object from system memory, it can at least make an object eligible for removal by eliminating all program references to it. To eliminate all references to an object, we must manually remove it from any arrays that contain it and assign null (or some other value) to any variable that references it. Making an object eligible for garbage collection does not immediately remove that object from memory. It simply gives ActionScript authorization to remove the object when and if a garbage collection cycle occurs. Note, however, that creating and then removing objects from memory is often less efficient than reusing objects. Wherever possible, you should strive to reuse objects rather than dis- pose them. Returning, once again, to the virtual zoo program, consider the following code. It feeds a pet five apples. package { import flash.display.Sprite; import zoo.*; public class VirtualZoo extends Sprite { private var pet:VirtualPet; public function VirtualZoo ( ) { pet = new VirtualPet("Stan"); pet.eat(new Apple( )); pet.eat(new Apple( )); pet.eat(new Apple( )); pet.eat(new Apple( )); pet.eat(new Apple( )); } } } Notice that each time the preceding code feeds the pet, it creates a new Apple instance and passes that instance to the eat( ) method. Each time the eat( ) method completes, all references to the Apple instance it was passed are lost, so the Apple 274 | Chapter 14: Garbage Collection instance becomes eligible for garbage collection. Now consider what would happen if we were to feed the pet 1,000 Apple objects. Our program would incur the process- ing cost not only of creating the Apple objects but also of garbage collecting them. To avoid that cost, we’re better off creating a single, reusable Apple object, and using that single object any time the pet eats an apple. The following code demonstrates by feeding the pet the same Apple object five times: package { import flash.display.Sprite; import zoo.*; public class VirtualZoo extends Sprite { private var pet:VirtualPet; private var apple:Apple; public function VirtualZoo ( ) { pet = new VirtualPet("Stan"); apple = new Apple( ); pet.eat(apple); pet.eat(apple); pet.eat(apple); pet.eat(apple); pet.eat(apple); } } } The preceding pet-feeding code incurs the cost of a single object-creation and incurs no garbage collection cost whatsoever. That’s much more efficient than our earlier approach of creating and garbage collecting a new Apple object for each eat( ) method invocation! Deactivating Objects We’ve learned that removing all references to an object makes that object eligible for garbage collection. However, even after an object becomes eligible for garbage collec- tion, it continues to exist in memory until ActionScript decides to “sweep” it away during a garbage collection cycle. After the object becomes eligible for garbage col- lection, but before it is actually removed from system memory, the object continues to receive events and, in the case of Function objects, can still be triggered by setInterval( ). For example, imagine a slideshow application that uses a class, ImageLoader, to load images from a server at regular intervals. The code for the ImageLoader class is as follows: package { import flash.events.*; import flash.utils.*; Deactivating Objects | 275 public class ImageLoader { private var loadInterval:int; public function ImageLoader (delay:int = 1000) { loadInterval = setInterval(loadImage, delay); } public function loadImage ( ):void { trace("Now loading image..."); // Image-loading code not shown } } } Further imagine that the application’s main class, SlideShow, implements code to start and stop the slideshow. To start the slideshow, SlideShow creates an ImageLoader instance that manages image loading. The ImageLoader instance is stored in the instance variable imgLoader, as follows: imgLoader = new ImageLoader( ); To stop or pause a slideshow, SlideShow discards the ImageLoader instance, as follows: imgLoader = null; When imgLoader is set to null, the ImageLoader instance becomes eligible for gar- bage collection. However, until the instance is actually removed from system mem- ory, the setInterval( )-based load operation in the ImageLoader instance continues executing on a regular basis. The following very simple class demonstrates. It creates and then immediately discards an ImageLoader instance. But even after imgLoader is set to null, the message “Now loading image...” continues to appear in the debug- ging console, once per second. package { import flash.display.*; public class SlideShow extends Sprite { private var imgLoader:ImageLoader; public function SlideShow ( ) { // Create and immediately discard an ImageLoader instance imgLoader = new ImageLoader( ); imgLoader = null; } } } If the memory required by the slideshow application never becomes significant enough to trigger a garbage collection cycle, then the setInterval( )-based load opera- tion in the ImageLoader instance will execute indefinitely. The unnecessary execu- tion of code in the discarded ImageLoader instance wastes system and network resources, and could cause undesired side effects in the slideshow program. 276 | Chapter 14: Garbage Collection To avoid unnecessary code execution in discarded objects, a program should always deactivate objects before discarding them. Deactivating an object means putting the object in an idle state where nothing in the program can cause it to execute code. For example, to deactivate an object, we might perform any or all of the following tasks: • Unregister the object’s methods for events • Stop all timers and intervals • Stop the playhead of timelines (for instances of movie clips created in the Flash authoring tool) • Deactivate any objects that would become unreachable if the object, itself, became unreachable To allow objects to be deactivated, any class whose instances register for events or uses timers should provide a public method for deactivating instances. For example, our preceding ImageLoader class should have provided a method to stop its interval. Let’s add such a method now and call it dispose( ). The name dispose( ) is arbitrary; it could also be called kill( ), destroy( ), die( ), clean( ), disable( ), deactivate( ), or anything else. Here’s the code: package { import flash.events.*; import flash.utils.*; public class ImageLoader { private var loadInterval:int; public function ImageLoader (delay:int = 1000) { loadInterval = setInterval(loadImage, delay); } public function loadImage ( ):void { trace("Now loading image..."); // Image-loading code not shown } public function dispose ( ):void { clearInterval(loadInterval); } } } Any code that creates an ImageLoader instance would then be required to invoke ImageLoader.dispose( ) before discarding it, as follows: package { import flash.display.*; public class SlideShow extends Sprite { private var imgLoader:ImageLoader; public function SlideShow ( ) { Garbage Collection Demonstration | 277 // Create and immediately discard an ImageLoader instance imgLoader = new ImageLoader( ); imgLoader.dispose( ); imgLoader = null; } } } Garbage Collection Demonstration Example 14-1 shows a very simple program that demonstrates garbage collection at work. The program creates a Sprite object that displays a message repeatedly in the debugging output console. Because the Sprite object is reachable only via a local vari- able, it becomes eligible for garbage collection immediately after the program’s main class constructor completes. Meanwhile, the program also runs a timer that repeat- edly creates objects, occupying system memory. When enough system memory is consumed, the garbage collector runs. During garbage collection, the original Sprite object is removed from memory, and its messages stop appearing in the debugging output console. Example 14-1. Garbage collection demonstration package { import flash.display.*; import flash.text.*; import flash.utils.*; import flash.events.*; import flash.system.*; public class GarbageCollectionDemo extends Sprite { public function GarbageCollectionDemo ( ) { // This Sprite object is garbage collected after enough memory // is consumed var s:Sprite = new Sprite( ); s.addEventListener(Event.ENTER_FRAME, enterFrameListener); // Repeatedly create new objects, occupying system memory var timer:Timer = new Timer(1, 0); timer.addEventListener(TimerEvent.TIMER, timerListener); timer.start( ); } private function timerListener (e:TimerEvent):void { // Create an object to take up some system memory. Could be // any object, but TextField objects are nice and meaty. new TextField( ); } // This function is executed until the Sprite object is // garbage collected private function enterFrameListener (e:Event):void { 278 | Chapter 14: Garbage Collection On to ActionScript Backcountry Garbage collection is an immensely important part of ActionScript programming. You should consider memory management in every ActionScript program you write. If you create an object, you should decide whether that object is needed for the entire lifespan of the program. If not, you should add code that deactivates and then dis- poses of the object. For more general information on garbage collection in programming languages, see “Garbage collection (computer science)” in Wikipedia, The Free Encyclopedia at http://en.wikipedia.org/wiki/Garbage_collection_(computer_science), and The Mem- ory Management Reference, at http://www.memorymanagement.org. For a series of self-published articles on garbage collection in ActionScript 3.0 by Grant Skinner, see http://www.gskinner.com/blog/archives/2006/06/as3_resource_ma. html and http://gskinner.com/talks/resource-management/. In the next chapter, we’ll explore some of ActionScript’s less commonly used tools for altering the structure of classes and objects at runtime. // Display the amount of memory occupied by this program trace("System memory used by this program: " + System.totalMemory); } } } Example 14-1. Garbage collection demonstration (continued) 279 Chapter 15 CHAPTER 15 Dynamic ActionScript16 ActionScript was originally conceived of as a language for adding basic program- matic behavior to content created manually in the Flash authoring tool. In early ver- sions of ActionScript, most code was intended to be written in short scripts that implemented limited functionality compared with the code required to create a com- plexdesktop application. As such, ActionScript’s original feature set stressed flexibil- ity and simplicity over formality and sophistication. ActionScript originally allowed the structure of all classes and even all individual objects to be modified dynamically at runtime. For example, at runtime, a program could: • Add new instance methods or instance variables to any class • Add new instance methods or instance variables to any single, specific object • Create a new class entirely from scratch • Change a given class’s superclass With the advent of ActionScript 3.0, Flash Player 9, Adobe AIR, and Flex, the Flash platform has evolved to a stage where the complexity of an ActionScript-based pro- gram may well rival the complexity of a full-featured desktop application. Accord- ingly, as a language, ActionScript has taken on many of the formal structures required for large-scale application development—structures such as a formal class keyword an inheritance syntax, formal datatypes, a built-in event framework, excep- tion handling, and built-in XML support. Nevertheless, ActionScript’s dynamic fea- tures remain available in the language and still constitute an important part of ActionScript’s internal makeup. This chapter explores ActionScript’s dynamic programming techniques. Note, how- ever, that the flexibility inherent in dynamic ActionScript programming limits or removes most of the benefits of type checking we studied in Chapter 8. As a result, most complexprograms use the features described in this chapter only sparingly, if at all. For example, in the over 700 classes defined by the Flex framework, there are approximately only 10 uses of dynamic programming techniques. That said, even if 280 | Chapter 15: Dynamic ActionScript you never intend to use dynamic programming in your code, the information presented in this chapter will improve your understanding ActionScript’s internal workings. For our first dynamic programming technique, we’ll study dynamic instance vari- ables—instance variables added to an individual object at runtime. Dynamic Instance Variables The very beginning of this book compared writing an ActionScript program to designing and building an airplane. The comparison likened the airplane’s blue- prints to ActionScript’s classes, and the actual parts in a specific physical airplane to ActionScript’s objects. In that analogy, a single individual airplane is guaranteed to have the same structure as all other airplanes because all airplanes are based on the same blueprint. By comparison, all instances of a given class are guaranteed to have the same structure because they are based on the same class. But what if the owner of a specific airplane mounts a custom light on the top of his airplane? That airplane now has a specific characteristic not shared by all other air- planes. In ActionScript, “adding a new light to an individual airplane” is analogous to adding a new instance variable to a single, specific object without adding that light to any other instance of that object’s class. Such an instance variable is known as a dynamic instance variable. When contrasted with dynamic instance variables, “regu- lar” instance variables are referred to as fixed variables. Dynamic instance variables can be added to instances of classes defined with the attribute dynamic only (such classes are referred to as dynamic classes). Dynamic instance variables cannot be added to instances of classes that are not defined with the attribute dynamic (such classes are referred to as sealed classes). A subclass of a dynamic class is not considered dynamic unless its definition also includes the dynamic attribute. The following code creates a new class, Person, which might represent a person in a statistics program that tracks demographics. Because Person is declared with the attribute dynamic, dynamic instance variables can be added to any individual Person object at runtime. dynamic public class Person { } Once a class has been defined as dynamic, we can add a new dynamic instance vari- able to any instance of that class via a standard variable assignment statement. For example, the following code adds a dynamic instance variable, eyeColor,toaPerson object: var person:Person = new Person( ); person.eyeColor = "brown"; Dynamic Instance Variables | 281 As we learned in Chapter 8, if the Person class were not declared dynamic, the pre- ceding code would cause a reference error because the Person class does not define an instance variable named eyeColor. However, in this case, the Person class is declared dynamic. As a result, when ActionScript encounters the attempt to assign a value to the nonexistent instance variable eyeColor, rather than reporting an error, it simply creates a dynamic instance variable named eyeColor, and assigns it the speci- fied value (“brown”). In the preceding code, notice that the dynamic instance vari- able definition does not, and must not, include a datatype definition or an access- control modifier. All dynamic instance variables are untyped and public. The following code, which attempts to include a datatype definition when creating eyeColor, causes a compile-time error: person.eyeColor:String = "brown"; // Error! :String is not allowed here To retrieve the value of a dynamic instance variable, we use a standard variable access expression as shown in the following code: trace(person.eyeColor); // Displays: brown If the specified instance variable (dynamic or not dynamic) does not exist, Action- Script returns the value undefined, as shown in the following code: trace(person.age); // Displays undefined because the person object // has no instance variable named age ActionScript’s most significant use of dynamic instance variables occurs in the Flash authoring tool, where each animated timeline is represented by a subclass of the built-in MovieClip class. In the Flash authoring tool, automatically generated sub- classes of MovieClip are dynamic so that programmers can define new variables on manually created movie clip instances. For details on this technique, and other time- line-scripting techniques, see Chapter 29. Dynamic instance variables are sometimes used to create a simple “lookup table,” as discussed later in the section “Using Dynamic Instance Variables to Create Lookup Tables.” Remember that allowing a program to be modified dynamically can lead to hard-to- diagnose problems. For example, when a class is defined as dynamic in order to sup- port dynamic instance variables, then legitimate reference errors made through that class’s instances can easily go unnoticed (because referencing an instance variable that does not exist generates neither a compile-time error nor a runtime error). The only way to know for sure whether the dynamically modified program works is to run it and observe its behavior. Such observation is time-consuming and prone to 282 | Chapter 15: Dynamic ActionScript human error, so most programmers avoid using dynamic variables in complexpro- grams. ActionScript takes longer to access a dynamic instance variable than it does to access a fixed variable. Where performance is a concern, avoid using dynamic instance variables. Processing Dynamic Instance Variables with for-each-in and for-in Loops The for-each-in loop provides easy access to the values of an object’s dynamic instance variables (or an array’s elements). It takes the general form: for each (variableOrElementValue in someObject) { statements } The statements of a for-each-in loop run once for each dynamic instance variable or array element of someObject. During each iteration of the loop, the value of the vari- able or element being iterated over (enumerated) is assigned to the variable variableOrElementValue. Code within the body of the loop then has the opportunity to operate on that value in some way. For example, consider the object definition and subsequent for-each-in loop shown in the following code. Note that the built-in Object class is declared dynamic, and, hence, supports dynamic instance variables. var info:Object = new Object( ); info.city = "Toronto"; info.country = "Canada"; for each (var detail:* in info) { trace(detail); } The preceding loop runs twice, once for each of the two dynamic instance variables of the object referenced by info. The first time the loop runs, the variable detail is assigned the value “Toronto” (i.e., the value of the city variable). The second time the loop runs, detail has the value “Canada” (i.e., the value of the country variable). So the output of the loop is: Toronto Canada Dynamic Instance Variables | 283 In most cases, the order in which for-each-in and for-in loops enumer- ate an object’s variables is not guaranteed. There are two exceptions: variables of XML and XMLList instances are enumerated in ascending sequential order according to their numeric variable names (i.e., docu- ment order for XML objects; see Chapter 18). For all other types of objects, the enumeration order used by for-each-in and for-in loops might vary across different versions of ActionScript or different Flash runtimes. Therefore, you should not write code that depends on a for- each-in or for-in loop’s enumeration order unless you are processing XML data. The following code shows a for-each-in loop used to access the values of an array’s elements: var games:Array = ["Project Gotham Racing", "Shadow of the Colossus", "Legend of Zelda"]; for each (var game:* in games) { trace(game); } The preceding loop runs three times, once for each of the three elements in the games array. The first time the loop runs, the variable game is assigned the value “Project Gotham Racing” (i.e., the first element’s value). The second time the loop runs, game has the value “Shadow of the Colossus.” And the third time it has the value “Legend of Zelda.” So the output of the loop is: Project Gotham Racing Shadow of the Colossus Legend of Zelda The for-each-in loop is a companion for the ActionScript for-in loop. Whereas the for-each-in loop iterates over variable values, the for-in loop iterates over variable names. For example, the following for-in loop lists the names of dynamic instance variables of the object referenced by info: for (var detailName:* in info) { trace(detailName); } // Output: city country Notice that the preceding code outputs the names of the variables city and country, not the values. To access the values of those properties, we could use the [] operator, as discussed in the later section “Dynamic References to Variables and Methods.” The following code demonstrates: for (var detailName:* in info) { trace(info[detailName]); } 284 | Chapter 15: Dynamic ActionScript // Output: Toronto Canada To prevent a dynamic instance variable from being included in for-in and for-each-in loops, use the Object class’s setPropertyIsEnumerable( ) method, as shown in the fol- lowing code: info.setPropertyIsEnumerable("city", false); for (var detailName:* in info) { trace(info[detailName]); } // Outputs: Canada // (the "city" variable was not processed by the for-in loop) We’ll see the for-each-in loop used in a practical situation in the later section, under “Using Dynamic Instance Variables to Create Lookup Tables.” Dynamically Adding New Behavior to an Instance Having just learned how to create dynamic instance variables, you might wonder whether ActionScript also supports dynamic instance methods—adding a new instance method to a single, specific object without adding it to any other instances of that object’s class. In fact, there is no formal means of adding a true instance method to an object dynamically. However, by assigning a function closure to a dynamic instance variable, we can emulate the effect of giving an individual object a new method (for a refresher on the term function closure, see Chapter 5). The follow- ing code demonstrates the general approach: someObject.someDynamicVariable = someFunction; In the preceding code, someObject is an instance of a dynamic class, someDynamicVariable is the name of a dynamic instance variable (which can be a new variable or an existing variable), and someFunction is a reference to a function clo- sure. Typically, someFunction is supplied using a function literal, as shown in the fol- lowing code: someObject.someDynamicVariable = function (param1, param2, ...paramn) { // Function body } Once someFunction has been assigned to the dynamic instance variable, it can be invoked through the object, exactly like a regular instance method, as shown in the following code: someObject.someDynamicVariable(value1, value2, ...valuen); Dynamically Adding New Behavior to an Instance | 285 To demonstrate the preceding generic syntax, let’s return to the info example from the preceding section: var info:Object = new Object( ); info.city = "Toronto"; info.country = "Canada"; Suppose we want to give the object referenced by info a new behavior—the ability to format and return its details as a string. We create a new instance variable, getAddress, to which we assign the desired formatting function, as follows: info.getAddress = function ( ):String { return this.city + ", " + this.country; } To invoke the function, we use the following code: trace(info.getAddress( )); Notice that within the body of the function assigned to getAddress, we can use the keyword this to access the variables and methods of the object through which the function was invoked. In fact, in the case of function closures, the variables and methods of the object through which the function was invoked cannot be accessed without the keyword this. For example, suppose we omit this from the getAddress( ) function definition, as follows: info.getAddress = function ( ):String { return city + ", " + country; } When searching for the variables city and country, ActionScript does not automati- cally consider instance variables of the object referenced by info. Therefore, the pre- ceding code causes an error (unless other variables by the name city and country actually exist higher up in the getAddress( ) function’s scope chain). If the assignment of a function to an object’s instance variable occurs within that object’s class, then the function can access the object’s private, protected, internal, and public variables and methods. If the assignment occurs within a subclass of the object’s class, then the function can access the object’s protected, internal, and public variables and methods only. If the assignment occurs within the same package as the object’s class, then the function can access the object’s internal and public variables and methods only. If the assignment occurs in a different package than the object’s class, then the function can access the object’s public variables and methods only. For example, consider the following code, which creates a dynamic class, Employe: dynamic public class Employee { public var startDate:Date; private var age:int; } The following code assigns a function to a dynamic instance variable, doReport,ofan Employee instance. The assignment occurs outside the Employee class, but within a 286 | Chapter 15: Dynamic ActionScript class in the same package as the Employee class. As a result, the function can access the Employee object’s internal and public variables, but not its protected or private variables. public class Report { public function Report (employee:Employee) { // Assign a function to doReport employee.doReport = function ( ):void { trace(this.startDate); // Access to public variable allowed trace(this.age); // Access to private variable denied } } } Dynamic References to Variables and Methods Because dynamic instance variable names are often not known until runtime, Action- Script provides a way to specify a variable’s name using an arbitrary string expres- sion. The following code shows the general approach: someObject[someExpression] In the preceding code, someObject is a reference to the object whose variable is being accessed, and someExpression is any expression that yields a string (indicating that variable’s name). The preceding code can be used both to assign a value to a variable and to retrieve a variable’s value. For example, the following code assigns the value “Toronto” to a variable whose name is specified by the literal string expression “city”: var info:Object = new Object( ); info["city"] = "Toronto"; The following code assigns the value “Canada” to a variable whose name is specified by the literal string expression “country”: info["country"] = "Canada"; The following code retrieves the value of the variable whose name is specified by the identifier expression, detail: var detail:String = "city"; trace(info[detail]); // Displays: Toronto When ActionScript encounters the code, info[detail], it first determines the value of detail, which is “city,” and then looks up the variable named “city” of the object referenced by info. The syntactic rules for identifiers don’t apply to variables created using the [] opera- tor. For example, the following code creates a dynamic instance variable whose name starts with a number: var info:Object = new Object( ); info["411"] = "Information Line"; Using Dynamic Instance Variables to Create Lookup Tables | 287 Using the dot (.) operator to create the same variable causes an error because it vio- lates the syntactic rules for identifiers: var info:Object = new Object( ); info.411 = "Information Line"; // ERROR! Identifiers must not start // with a number Note that the preceding technique can be used to access any kind of variable or method, not just dynamic instance variables; but it’s most commonly used with dynamic instance variables. The next section shows dynamic references used in an applied situation: creating a lookup table. Using Dynamic Instance Variables to Create Lookup Tables A lookup table is a data structure that maps a set of names to a corresponding set of values. For example, the following pseudocode shows a lookup table representing the courses of a meal: appetizer: tortilla chips maincourse: bean burrito dessert: cake To represent the lookup table using dynamic instance variables, we would use the following code: var meal:Object = new Object( ); meal.appetizer = "tortilla chips"; meal.maincourse = "bean burrito"; meal.dessert = "cake"; Now let’s consider a more involved scenario. Imagine an inventory application for a bookstore in which the user can browse books by ISBN number. Information for each book is loaded from an external server. To minimize communication with the server, the application loads the information for 500 books at a time. For the sake of simplicity, we’ll assume that each book’s information takes the form of a single string, in the following format: "Price: $19.99. Title: Path of the Paddle" To store the loaded book information in ActionScript, we create an instance of the Object class, which will act as a lookup table for the books: var bookList:Object = new Object( ); As each book’s information is loaded, we assign it to a new dynamic instance vari- able of the preceding bookList object. Each variable’s name corresponds to a book’s ISBN number, preceded by the string “isbn”. For example, the variable for a book with the ISBN number 155209328X would be named isbn155209328X. The following 288 | Chapter 15: Dynamic ActionScript code demonstrates how we would create a given book’s dynamic instance variable if we knew its ISBN number in advance: bookList.isbn155209328X = "Price: $19.95. Title: Path of the Paddle"; In the real application, however, we won’t know a book’s ISBN number until it is loaded from the server. Hence, we must define each book’s dynamic-instance vari- able name dynamically, based on data loaded at runtime. For the sake of demonstra- tion, let’s create a variable, bookData, whose value represents the data as it would be loaded from the server. In this simplified example, each book’s ISBN number and details are separated with a single tilde character (~). Meanwhile, entire books are separated from each other by two tilde characters (~~). var bookData:String = "155209328X~Price: $19.95. Title: Path of the Paddle" + "~~" + "0072231726~Price: $24.95. Title: High Score!"; To convert the loaded book data from a string to an array of books for processing, we use the String class’s split( ) method, as follows: var bookDataArray:Array = bookData.split("~~"); To convert the array of books to a lookup table, we use the following code: // Create a variable to track each book's information as it is processed var book:Array; // Loop once for every item in the array of books for (var i:int = 0; i < bookDataArray.length; i++) { // Convert the current item in the array from a string to its // own array. For example, the string: // "155209328X~Price: $19.95. Title: Path of the Paddle" // becomes the array: // ["155209328X", "Price: $19.95. Title: Path of the Paddle"] book = bookDataArray[i].split("~"); // Create a dynamic instance variable whose name matches the ISBN number // of the current item in the array of books, and assign that variable // the description of the current item in the array. Note that the ISBN // number is book[0], while the description is book[1]. bookList["isbn" + book[0]] = book[1]; } Once we’ve added all 500 books to the bookList object, each with its own dynamic instance variable, the user can then select a book to view by entering its ISBN num- ber in a text-input field, isbnInput. Here’s how we would display the user’s selected book during debugging: trace(bookList["isbn" + isbnInput.text]); Here’s how we would display the user’s selected book on screen in a text field refer- enced by bookDescription: bookDescription.text = bookList["isbn" + isbnInput.text]; Using Functions to Create Objects | 289 To list all of the books in the bookList object, we would use a for-each-in loop, as follows: for each (var bookInfo:* in bookList) { // Display the value of the dynamic instance variable currently // being processed trace(bookInfo); } The preceding loop produces the following debugging output: Price: $19.95. Title: Path of the Paddle Price: $24.95. Title: High Score! Making Lookup Tables with Object Literals For the sake of convenience, to create a lookup table whose content is limited and known in advance, we can use an object literal. An object literal creates a new instance of the Object class from a series of comma-separated dynamic-variable name/value pairs, enclosed in curly braces. Here’s the general syntax: {varName1:varValue1, varName2:varValue2,...varNameN:varValueN} For example, the following code creates an Object instance with a dynamic instance variable named city (whose value is “Toronto”), and a dynamic instance variable named country (whose value is “Canada”): var info:Object = {city:"Toronto", country:"Canada"}; The preceding code is identical to the following code: var info:Object = new Object( ); info.city = "Toronto"; info.country = "Canada"; If there were only two books in the preceding section’s book-inventory application, we might have used the following object literal to create the bookList lookup table: var bookList:Object = { isbn155209328X:"Price: $19.95. Title: Path of the Paddle", isbn0072231726:"Price: $24.95. Title: High Score!" }; Using Functions to Create Objects As we’ve seen throughout this book, most objects in ActionScript are created using classes. However, it is also possible to create objects using standalone function clo- sures. The following code shows the basic approach. It uses an example function, Employee( ), to create an object: // Create the function function Employee ( ) { } 290 | Chapter 15: Dynamic ActionScript // Use the function to create an object, and assign that // object to the variable, worker var worker = new Employee( ); Notice that the variable worker is untyped. From a datatype perspective, the object referenced by worker is an instance of the Object class. There is no Employee class, so there is no Employee datatype. The following code, therefore, causes an error (because the Employee datatype does not exist): // ERROR! var worker:Employee = new Employee( ); A function closure used to create an object is referred to as a constructor function (not to be confused with a constructor method, which is part of a class definition). In ActionScript 3.0, standalone functions declared at the package-level cannot be used as constructor functions. Hence, the preceding code would have to appear within a method, in code outside of a package statement or in a frame script on a timeline in the Flash authoring tool. For the sake of brevity, however, this section shows all con- structor functions outside the required containing method or frame script. All objects created from constructor functions are implicitly dynamic. Hence, a con- structor function can use the this keyword to add new dynamic instance variables to an object at creation time. Dynamic instance variables created in a constructor func- tion are typically assigned values passed to the function as arguments. The following code demonstrates: function Employee (age, salary) { // Define dynamic instance variables this.age = age; this.salary = salary; } // Pass arguments to use as the values of // this object's dynamic instance variables var worker = new Employee(25, 27000); trace(worker.age); // Displays: 25 To allow objects created via a particular constructor function to share information and behavior, ActionScript defines a special static variable named prototype on every function. A function’s prototype variable references an object (known as the func- tion’s prototype object) whose dynamic instance variables can be accessed through any object created by that function. Initially, ActionScript assigns every function’s prototype variable an instance of the generic Object class. By adding dynamic instance variables to that object, we can create information and behavior that is shared by all objects created from a particular function. For example, the following code adds a dynamic instance variable, company, to the Employee( ) function’s prototype object: Employee.prototype.company = "AnyCorp"; Using Prototype Objects to Augment Classes | 291 As a result, any object created from the Employee( ) function can access company as though it were its own dynamic instance variable: var worker = new Employee(25, 27000); trace(worker.company); // Displays: AnyCorp In the preceding code, when ActionScript realizes that the object created from the Employee( ) function (worker) does not have an instance variable or instance method named “company,” it then checks whether Employee.prototype defines a dynamic instance variable by that name. The Employee.prototype object does define such a variable, so ActionScript uses it as though it were the worker object’s own variable. If, on the other hand, the worker object defined its own variable named company, then that variable would be used instead of the Employee.prototype object’s variable. The following code demonstrates: var worker = new Employee(25, 27000); worker.company = "CarCompany"; trace(worker.company); // Displays: CarCompany (not AnyCorp) Using the technique we learned in the section “Dynamically Adding New Behavior to an Instance,” we can assign a function to a dynamic instance variable of any con- structor function’s prototype object. The function can then be used by any object cre- ated from that constructor function. For example, the following code defines a dynamic instance variable, getBonus,on the Employee( ) function’s prototype object and assigns that variable a function that calculates and returns an annual bonus: Employee.prototype.getBonus = function (percentage:int):Number { // Return a bonus based on a specified percentage of the // employee's salaray return this.salary * (percentage/100); } As a result, all objects created from the Employee( ) function can use the getBonus( ) function as though it were assigned to their own dynamic instance variable: var worker = new Employee(25, 27000); trace(worker.getBonus(10)); // Displays: 2700 Using Prototype Objects to Augment Classes We’ve learned that ActionScript defines a special static variable named prototype on every function. Using the prototype variable of a given function, we can share infor- mation and behavior among all objects created from that function. Just as ActionScript defines a prototype variable on every function, it also defines a static prototype variable on every class. Using the static prototype variable, we can add shared information and behavior to all instances of a given class at runtime. 292 | Chapter 15: Dynamic ActionScript For example, the following code defines a new dynamic instance variable, isEmpty, on the built-in String class’s prototype object and assigns that variable a function. The function returns true when a string has no characters in it; otherwise the func- tion returns false: String.prototype.isEmpty = function ( ) { return (this == "") ? true : false; }; To invoke the function isEmpty( ) on a String object, we use the following code: var s1:String = "Hello World"; var s2:String = ""; trace(s1.isEmpty( )); // Displays: false trace(s2.isEmpty( )); // Displays: true However, the previous code example—and this entire technique—has a problem: the dynamic instance variable isn’t added until runtime; therefore, the compiler has no idea that it exists and will generate an error if it is used in strict mode. For exam- ple, in strict mode, the code in the preceding example yields this error: Call to a possibly undefined method isEmpty through a reference with static type String. In order to refer to isEmpty( ) in strict mode without causing a compile-time error, we must use a dynamic reference, as shown in the following code: s1["isEmpty"]( ) On the other hand, if the String class were declared dynamic, then the original approach (i.e., s1.isEmpty( )) would not generate an error. Note that fixed variables and methods are always preferred over prototype variables. In the preceding example, if the String class already defined an instance method or instance variable named isEmpty, then all references to isEmpty would refer to that instance variable or instance method—not to dynamic instance variable on the String class’s prototype object. The Prototype Chain In the preceding sections, we learned that a prototype object can be used to share information and behavior with the objects created from a particular constructor function or class. In fact, the reach of a given prototype object goes beyond the objects created from the function or class to which it is attached. In the case of a class, the dynamic instance variables defined on the class’s prototype object can be accessed not just through the class’s instances but also through the instances of the class’s descendants. The following generic code demonstrates: // Create a simple class, A dynamic public class A { The Prototype Chain | 293 } // Create another simple class, B, that extends A dynamic public class B extends A { } // Create an application's main class public class Main extends Sprite { public function Main ( ) { // Add a dynamic instance variable to class A's prototype object A.prototype.day = "Monday"; // Access A.prototype.day through an instance of B var b:B = new B( ); trace(b.day); // Displays: "Monday" } } In the case of a function, the dynamic instance variables defined on the function’s prototype object can be accessed not just through any object created from that func- tion but also through any object whose prototype chain includes that function’s pro- totype object. Let’s explore how prototype chains work through an example. Suppose we create a function, Employee( ), just as we did earlier, whose prototype object has a dynamic instance variable named company: function Employee ( ) { } Employee.prototype.company = "AnyCorp"; Any object created from Employee( ) can access company through Employee( )’s proto- type object. Nothing new so far. Now suppose we create another function, Manager( ): function Manager ( ) { } Suppose also that we wish to give objects created from Manager( ) access to company through Employee( )’s prototype object. To do so, we assign an object created from Employee( ) to Manager( )’s prototype variable. Manager.prototype = new Employee( ); Now, let’s consider what happens when we access the name “company” through an object created from the Manager( ) function, as follows: var worker = new Manager( ); trace(worker.company); When the preceding code runs, ActionScript checks whether the worker object has an instance variable or instance method named “company.” The worker object does not have an instance variable or instance method by that name, so ActionScript then checks whether the Manager( ) function’s prototype object defines a dynamic instance variable named “company.” The Manager( ) function’s prototype object is, 294 | Chapter 15: Dynamic ActionScript itself, an object created from the Employee( ) function. However, objects created from the Employee( ) function do not define a dynamic instance variable named “company.” Hence, ActionScript next checks whether the Employee( ) function’s prototype object defines a dynamic instance variable named “company.” The Employee( ) function’s prototype object does define such a variable, so ActionScript uses it as though it were the worker object’s own variable. Here’s the trail ActionScript follows to find “company”: 1. Search worker for company. Not found. 2. Search Manager.prototype for company. Not found. 3. Manager.prototype was created from Employee( ), so search Employee.prototype for company. Found! The list of prototype objects ActionScript searches when attempting to resolve a vari- able’s value is known as the prototype chain. Prior to ActionScript 3.0, the prototype chain was the primary mechanism for sharing reusable behavior among various kinds of objects. As of ActionScript 3.0, class inheritance plays that role. Note the following limitations imposed on the prototype chain in ActionScript 3.0: • An object assigned to a function’s prototype variable must, itself, be an object created from a function or an instance of the Object class (instances of other classes are not allowed). • The value of a class’s prototype variable is set automatically by ActionScript and cannot be reassigned. Onward! In most mid- to large-scale projects, the dynamic techniques we learned in this chap- ter play only a minor role. Nevertheless, an understanding of ActionScript’s dynamic programming features should increase your overall comfort with the language. In a similar way, knowledge of scope, the topic of the next chapter, will increase your confidence as an ActionScript programmer (but might not get you asked out on any dates). Scope governs the availability and life span of a program’s definitions. 295 Chapter 16 CHAPTER 16 Scope17 A scope is a physical region of a program in which code executes. In ActionScript there are five possible scopes: • A function body • An instance method body • A static method body • A class body • Everywhere else (i.e., global scope) At any specific point in the execution of a program, the availability of variables, func- tions, classes, interfaces, and namespaces is governed by the scope of the code cur- rently being executed. For example, code in a function can access that function’s local variables because it executes inside the function’s scope. By contrast, code out- side the function cannot access the function’s local variables because it executes out- side the function’s scope. In ActionScript, scopes can be nested. For example, a function might be nested in an instance method, which, itself, is nested in a class body: public class SomeClass { public function someMethod ( ):void { function someNestedFunction ( ):void { // This function's scope is nested inside someMethod( )'s scope, // which is nested inside SomeClass's scope } } } When one scope is nested within another, the definitions (i.e., variables, functions, classes, interfaces, and namespaces) available to the enclosing scope become avail- able to the nested scope. For example, a function nested inside an instance method can access that method’s local variables. The entire list of nested scopes surrounding the code currently being executed is known as the scope chain. 296 | Chapter 16: Scope This chapter describes the availability of variables, functions, classes, interfaces, and namespaces within ActionScript’s various scopes. Note that in addition to the definitions listed as “accessible” in each of the following sections, the public definitions of an external package can also be made visible in a given scope via the import directive. For details, see the section “Object Creation Example: Adding a Pet to the Zoo” in Chapter 1. In combination, a definition’s location and access-control modifier governs its acces- sibility throughout a program. For reference, Table 16-1 lists the accessibility of defi- nitions according to their location and access-control modifier. Global Scope Code placed directly outside a package body or at the top-level of a package body resides in the global scope. In other words: package { // Code here is in the global scope } // Code here is also in the global scope Code in the global scope can access the following definitions: • Functions, variables, classes, interfaces, and namespaces defined at the top level of the unnamed package • Functions, variables, classes, interfaces, and namespaces defined outside of any package, but in the same source (.as) file In other words: package { // Definitions here are accessible to all code in the global scope Table 16-1. Definition accessibility by location and access-control modifier Definition Accessibility Definition outside of any package Accessible within containing source file only Definition in the unnamed package Accessible to entire program Public definition in a named package Accessible within the package containing the definition and any- where the definition is imported Internal definition in a named package Accessible within the package containing the definition only Public method or variable Accessible anywhere the containing class can be accessed Internal method or variable Accessible within the containing class’s package Protected method or variable Accessible within the containing class and its descendant classes Private method or variable Accessible within the containing class only Definition in instance method, static method, or function Accessible within the containing method or function, and all of its nested functions Class Scope | 297 } // Definitions here are accessible to all code in the same source file Note that code placed at the top-level of a named package body can also access the definitions placed at the top-level of that package. Those definitions are accessible because within a named package, ActionScript automatically opens the namespace corresponding to that package (see Chapter 17). In other words: package somePackage { // Definitions here are automatically accessible to // all code in somePackage } Class Scope Code placed at the top-level of a class body resides in that class’s scope. Here’s the code: package { public class SomeClass { // Code here is in the someClass scope } } Remember that code placed at the top-level of a class body is wrapped in an automatically created static method (the class initializer), which executes when ActionScript defines the class at runtime. See the sec- tion “The Class Initializer” in Chapter 4. Via the scope chain, code in a classs scope can access the following definitions: • All definitions available to code in the global scope Additionally, code in a class’s scope can access the following definitions: • Static methods and static variables defined by the class • Static methods and static variables defined by the class’s ancestors, if any (i.e., superclass, superclass’s superclass, etc.) In other words: package { public class SomeClass extends SomeParentClass { // Static variables and static methods defined here are // accessible througout SomeClass } } package { public class SomeParentClass { // Static variables and static methods defined here are 298 | Chapter 16: Scope // accessible througout SomeClass } } Remember that even though a class can access its ancestors’ static variables and methods, static variables and methods are not inherited. See the section “Static Methods and Static Variables Not Inherited” in Chapter 6. Static Method Scope Code placed in a static method body resides in that method’s scope. To demon- strate: package { public class SomeClass { public static function staticMeth ( ) { // Code here is in the staticMeth scope } } } Via the scope chain, code in a static method’s scope can access these definitions: • All definitions available to code in the global scope • All definitions available to code in the scope of the class containing the static method definition Additionally, code in a static method’s scope can access the following definition: • All local variables, nested functions, and namespaces defined within the static method In other words: package { public class SomeClass extends SomeParentClass { public static function staticMeth ( ) { // Local variables, nested functions, and namespaces defined here // are accessible throughout staticMeth } } } Instance Method Scope Code placed in an instance method body resides in that method’s scope. Here’s the code: package { public class SomeClass { public function instanceMeth ( ) { // Code here is in the instanceMeth scope Function Scope | 299 } } } Via the scope chain, code in an instance method’s scope can access these definitions: • All definitions available to code in the global scope • All definitions available to code in the scope of the class containing the instance method definition Additionally, code in an instance method’s scope can access these definitions: • All instance methods and instance variables of the object through which the instance method was invoked (subject to the limitations imposed by access-con- trol modifiers) • All local variables, nested functions, and namespaces defined within the instance method The following code demonstrates: package { public class SomeClass extends SomeParentClass { public function instanceMeth ( ) { // 1) All instance methods and instance variables of the current // object (i.e., this) are accessible throughout instanceMeth( ) // (subject to the limitations imposed by access-control modifiers) // 2) Local variables, nested functions, and namespaces defined here // are accessible throughout instanceMeth( ) } } } Function Scope Code placed in a function body resides in that function’s scope. The specific list of definitions available to code in a function’s scope depends on the location of that function in the program. Code in a function defined at the package-level or outside any package can access the following definitions: • All definitions available to code in the global scope • All local variables, nested functions, and namespaces defined within the function Code in a function defined within a static method can access these definitions: • All definitions available to code in the global scope • All definitions available to code in the scope of the static method • All local variables, nested functions, and namespaces defined within the function 300 | Chapter 16: Scope Code in a function defined within an instance method can access the following definitions: • All definitions available to code in the global scope • All definitions available to code in the scope of the instance method • All local variables, nested functions, and namespaces defined within the function Code in a function defined within another function can access these definitions: • All definitions available to code in the global scope • All definitions available to code in the enclosing function • All local variables, nested functions, and namespaces defined within the function Scope Summary The following code summarizes ActionScript available scopes: package { // Code here is in the global scope public class SomeClass { // Code here is in the SomeClass scope public static function staticMeth ( ):void { // Code here is in the staticMeth scope } public function instanceMeth ( ):void { // Code here is in the instanceMeth scope function nestedFunc ( ):void { // Code here is in the nestedFunc scope } } } } // Code here is in the global scope The Internal Details Internally, ActionScript uses a list of objects to keep track of the definitions in the scope chain. The objects used to track the definitions of each scope are as follows: Global scope The global object (an object created automatically by ActionScript to hold global definitions) Class scope The class’s Class object (and the Class objects of the class’s ancestors) The Internal Details | 301 Static method scope The class’s Class object (and the Class objects of the class’s ancestors) Instance method scope The current object (this) and an activation object (an activation object is an object created and stored internally by ActionScript and maintains the local vari- ables and parameters of function or method) Function scope An activation object When ActionScript encounters an identifier expression in an program, it searches for that identifier among the objects in the scope chain. For example, consider the fol- lowing code: package { public class SomeClass { public function instanceMeth ( ):void { function nestedFunc ( ):void { trace(a); } } } } var a:int = 15; In the preceding code, when ActionScript encounters the identifier a, it begins a search for the value of that identifier with nestedFunc( )’s activation object. But nestedFunc( ) does not define any local variables or parameters named a, so ActionScript next searches for a on the current object (i.e., the object through which instanceMeth( ) was invoked). But SomeClass does not define or inherit an instance method or instance variable named a, so ActionScript next searches for a on SomeClass’s class object. But SomeClass does not define a static method or static variable named a, so ActionScript next searches for a on the class object of SomeClass’s superclass—which is Object. But Object does not define a static method or static variable named a, so ActionScript next searches for a on the global object. There, ActionScript finds a, and determines its value to be 15. With a’s value in hand, ActionScript then outputs 15 during debug- ging. Quite a lot of work for li’l ’ol a! Here are the objects ActionScript searched for a, in the order they were searched: • nestedFunc( )’s activation object • The object through which instanceMeth( ) was invoked • SomeClass’s class object • Object’s class object • The global object If a is not found on the global object, ActionScript reports a reference error. 302 | Chapter 16: Scope Note that in the preceding example, a is defined on the global object but is accessi- ble in the source file that contains a’s definition only. Variables defined outside a package definition are accessible within the containing source file only. Now that we know all about the scope chain, let’s close this chapter with a quick look at ActionScript’s only tool for manipulating the scope chain directly—the with statement. Expanding the Scope Chain via the with Statement The with statement provides a shorthand way to refer to the variables and methods of an object without having to specify the object’s name repeatedly. A with state- ment takes the general form: with (object) { substatements } When an identifier is referenced within a with statement block, object is checked for the specified name—before the remainder of the scope chain is consulted. In other words, with temporarily adds object to the end of ActionScript’s internal list of objects in the scope chain. For example, to refer to the built-in Math class’s PI variable, we normally use the fol- lowing code: Math.PI; But using the with statement, we can to refer to the built-in Math class’s PI variable without the preceding reference to the Math class: with (Math) { // Execute statements in the context of Math trace(PI); // Displays: 3.1459... (because PI is defined on Math) } Some developers find the with statement convenient when writing code that makes repeated references to a particular object’s variables and methods. On to Namespaces | 303 On to Namespaces In this chapter we learned how ActionScript manages the availability of definitions in different scopes. In the next chapter, we’ll learn to use namespaces to manage the visibility of definitions. Note that namespaces are an important part of Action- Script’s internal makeup but are typically used in custom code in advanced situa- tions only. Newer programmers may wish to skip the next chapter and proceed directly to Chapter 18. 304 Chapter 17CHAPTER 17 Namespaces 18 In very general terms, a namespace is a set of names that contains no duplicates. That is, within the set, each name is unique. For example, in English, the names of fruits could be considered a namespace because each fruit has its own unique name— apple, pear, orange, and so on. Likewise, the names of colors could be considered a namespace because each color has its own unique name—blue, green, orange, and so on. Notice that the name “orange” appears in both groups of names. The name “orange” itself is not unique, it is unique only within each group. Depending on whether you’re talking about a fruit or a color, the same name, “orange,” refers to two differ- ent things. That’s the purpose of namespaces. They let the same name (identifier) have different meanings depending on the context in which it is used. When applied to programming, this “same name, different meaning” feature of namespaces offers two general benefits: • It helps programmers avoid name conflicts • It lets a program’s behavior adapt to the current context Over the course of this chapter, we’ll explore the many nooks and crannies of namespaces in ActionScript. Try not to let the various details distract you from the relative simplicity of namespaces. Fundamentally, namespaces are nothing more than a two-part naming system. They are used to distinguish one group of names from another, much like an area code distinguishes one phone number from other phone numbers around the world. Namespace Vocabulary In this chapter, we’ll encounter quite a few new namespace-related terms. Below you’ll find some of the most important ones listed for quick reference. Skim the list quickly now for familiarity and return to it whenever you need a refresher during the ActionScript Namespaces | 305 upcoming discussions. The remainder of this chapter covers each of these terms in much greater depth. local name The local part of a qualified identifier; that is, the name being qualified by the namespace. For example, “orange” in fruit::orange. namespace name The uniquely identifying name of a namespace, in the form of a uniform resource identifier (URI). In ActionScript, the namespace name is accessible via the Namespace class’s instance variable uri. In XML, the namespace name is accessible via the xmlns attribute. namespace prefix An alias to the namespace name. Namespace prefixes are used in XML only, but can be accessed in ActionScript via the Namespace class’s prefix variable for the sake of E4X operations. namespace identifier The identifier used for a namespace in an ActionScript namespace definition. For example, fruit in the following namespace definition: namespace fruit = "http://www.example.com/games/kidsgame/fruit"; open namespace A namespace that has been added to the set of open namespaces via the use namespace directive. open namespaces The set of namespaces ActionScript consults when attempting to resolve unqual- ified references. qualifier namespace The namespace that qualifies a variable or method definition or the namespace identifier in a qualified identifier. qualified identifier A two-part ActionScript identifier that includes a namespace identifier and a local name, separated by two colons. For example, fruit::orange. ActionScript Namespaces In ActionScript, a namespace is a qualifier for the name of a variable, method, XML tag, or XML attribute. A qualifier limits, or “qualifies” the meaning of an identifier, giving us the ability to say in code “this orange variable is a fruit, not a color” or “this search( ) method applies to Japanese language searching, not English” or “this <TABLE> tag describes HTML page layout, not a piece of furniture.” 306 | Chapter 17: Namespaces Using ActionScript namespaces to qualify variable and method names we can: • Prevent naming conflicts (see the section “Namespaces for Access-Control Modi- fiers”) • Implement custom levels of method and variable visibility across an entire pro- gram, independent of the program’s package structure (see the mx_internal namespace example, in the section “Example: Framework-Internal Visibility”) • Implement permission-based access control wherein classes must request access to variables and methods (see Example 17-5 in the section “Example: Permis- sion-Based Access Control”) • Implement multiple “modes” in a program (see Example 17-7 in the section “Example: Program Modes”) ActionScript namespaces also provide direct access to XML namespaces in XML doc- uments. For coverage of XML namespaces in ActionScript see the section “Working with XML Namespaces” in Chapter 18. Before we get to applied namespace examples, let’s look at the basic concepts and syntaxinvolved in using ActionScript namespaces. Over the nextfew introductory sections we’ll create two namespaces: fruit and color, then use the fruit and color C++ Namespaces Versus ActionScript Namespaces Even though some of ActionScript’s namespace syntaxis similar to C++’s namespace syntax, namespaces are used differently in ActionScript than they are in C++. In C++, a namespace is a syntactic container, just as packages are in ActionScript and Java. In C++, an identifier is considered “in” a given namespace only if it physically resides in that namespace’s statement block. For example, in the following code, the variable a is in the namespace n because of the physical placement of the variable’s dec- laration within the namespace statement: namespace n { int a; } C++ namespaces are, therefore, used primarily to prevent naming conflicts between physical sections of code and to restrict one section of code from accessing another. By contrast, in ActionScript, a namespace can include any variable or method, regard- less of physical code structure. ActionScript namespaces define visibility rules for methods and variables that transcend the structural limits (classes and packages) of a program. C++ programmers looking for the equivalent of C++ namespaces in ActionScript should investigate ActionScript packages, covered in Chapter 1. There is no direct ana- log for ActionScript namespaces in C++. Creating Namespaces | 307 namespaces to qualify the definitions of two variables, and finally refer to those variables using so-called “qualified identifiers.” Along the way, we’ll progress towards a simple example application: a child’s learn-to-read game. Let’s get started. Creating Namespaces To create a namespace, we must give it a name. The name of each namespace— known formally as the namespace name—is a string that, by convention, specifies a uniform resource identifier, or URI. The URI uniquely identifies the namespace among all other namespaces in a program and potentially even among any program in the world. The term URI refers to the generalized resource-identification stan- dard of which the familiar Internet address standard, URL, is a sub- type. See http://www.ietf.org/rfc/rfc2396.txt. ActionScript’s use of URIs as namespace names is based on the stan- dard set by the World Wide Web Consortium (W3C) in their “Namespaces in XML” recommendation. See http://www.w3.org/TR/ xml-names11. The first step in creating a namespace, then, is to decide on a URI to use as its name. Choosing the Namespace URI Typically, the URI used as a namespace name is a URL within the control of the organization producing the code. For example, my web site is www.moock.org, so for a new namespace name, I might use a URI generally structured like this: http://www.moock.org/app/namespace For the child’s game that we’re going to build, we’ll use the following URIs for the namespaces fruit and color: http://www.example.com/games/kidsgame/fruit http://www.example.com/games/kidsgame/color Note that the URI need not—and generally does not—exist online. The URI is used only to identify the namespace; it is not a web page address, or an XML document address, or any other online resource. Any URI is allowed, but using a URL from your own web site as the namespace name minimizes the likelihood that other orga- nizations will use the same name. Defining the Namespace Once we’ve settled on a URI to use as the namespace name, we create the namespace using the namespace keyword, followed by the namespace’s identifier, then an equal sign, and finally the namespace name (the URI): 308 | Chapter 17: Namespaces namespace identifier = URI; The namespace identifier is the name of the ActionScript constant to which the namespace value is assigned (where the namespace value is an instance of the Namespace class automatically generated by the preceding statement). The URI is the namespace name. For example, to create a namespace with the identifier fruit and the URI "http:// www.example.com/games/kidsgame/fruit", we use: namespace fruit = "http://www.example.com/games/kidsgame/fruit"; To create a namespace with the identifier color and the URI "http://www.example. com/games/kidsgame/color", we use: namespace color = "http://www.example.com/games/kidsgame/color"; Notice that no datatype declaration is required or allowed. The implicit datatype of the namespace identifier is always Namespace. Namespaces can be defined any- where variables can be defined, namely: • In the top level of a package definition • In the top level of a class definition • In a function or method • On a movie clip timeline in a .fla file In practice, namespaces are nearly always defined at the top level of a package or class definition (unless they’re used for XML, as discussed in Chapter 18). For now, we’ll define all our namespaces at the package level. To create a package-level namespace for use throughout a program, place the namespace definition in a separate file, with the extension .as, and a name that exactly matches the namespace identifier’s name, as in the following for the fruit namespace: // File fruit.as package kidsgame { namespace fruit = "http://www.example.com/games/kidsgame/fruit"; } and for the color namespace: // File color.as package kidsgame { namespace color = "http://www.example.com/games/kidsgame/color"; } In the later section “Namespace Accessibility,” we’ll study examples of namespaces defined at the class or function level. Namespaces defined in a movie clip timeline are treated as though they were defined at the class level in the class representing the containing movie clip (for more information on timeline-level definitions, see the sec- tion “Variable and Function Definitions in Frame Scripts” in Chapter 29.) Creating Namespaces | 309 Explicit Versus Implicit URIs So far, all our namespace definitions have included an explicit URI, such as the one shown in bold in the following namespace definition: namespace fruit = "http://www.example.com/games/kidsgame/fruit"; But when a namespace declaration does not explicitly provide a namespace name (URI), ActionScript automatically generates one. For example, the following namespace definition provides no URI, so ActionScript automatically creates one: package { namespace ns1; } To prove it, we can display the automatically generated URI for the namespace ns1 like this: namespace ns1; trace(ns1.uri); // Displays: ns1 The Namespace class’s instance method toString( ) also returns the value of the instance variable uri, so the trace( ) call can be shortened to: trace(ns1); // Also displays: ns1 However, it’s generally wise to specify a URI when defining namespaces because explicit URIs are universally identifiable in any context, even across multiple .swf files, whereas automatically generated URIs are not. In this chapter, we’ll provide an explicit URI for all namespaces. As a best practice, always provide a URI for each namespace definition. Namespace Terminology Review In just a few short pages we’ve encountered quite a lot of new terminology. Let’s review. •Anamespace name is a URI that identifies a namespace. • The Namespace class represents a namespace in an object-oriented way. •Anamespace value is an instance of the Namespace class. •Anamespace identifier is the name of an ActionScript constant that refers to a namespace value. Generally speaking, the complexity of these terms can be captured by the simple term “namespace.” For example, in this book, we often use the simple phrase, “the namespace fruit,” in place of the more technically precise phrase, “the namespace 310 | Chapter 17: Namespaces "http://www.example.com/games/kidsgame/fruit" that is represented by a Namespace object referenced by the identifier fruit.” For the sake of easier reading, we’ll normally use the simpler, if less precise, phrase “the namespace fruit.” Nevertheless, the distinction between a namespace name and a namespace identifier is important to some of the ensuing discussion, so you should familiarize yourself with the preceding terminology. Using a Namespace to Qualify Variable and Method Definitions Now that we’ve defined the fruit and color namespaces, we can use them to specify the so-called qualifier namespace for new methods and variables. The qualifier namespace is the namespace within which the variable or method name is unique. To specify the qualifier namespace for a new variable or method, we use that namespace’s identifier as an attribute of the variable or method definition, as follows: // Specify the qualifier namespace for a variable namespaceIdentifier var propName:datatype = optionalInitialValue; // Specify the qualifier namespace for a method namespaceIdentifier function methodName (params):ReturnType { statements } The following rules apply to the namespaceIdentifier: • It must be accessible to the scope where the variable or method is defined, as dis- cussed later in the section “Namespace Accessibility.” • It cannot be a literal value; specifically it cannot be a string literal containing a namespace name (URI). • It must be a compile-time constant. These three rules effectively mean that the namespaceIdentifier can only be a namespace identifier created with the namespace keyword, and specifically cannot be a variable that refers to a namespace value. (We’ll learn about variables that refer to namespace values later in the section “Assigning and Passing Namespace Values.”) Here’s how to specify the qualifier namespace fruit for an instance variable named orange: fruit var orange:String = "Round citrus fruit"; And here’s how to specify the qualifier namespace color for an instance variable also named orange: color var orange:String = "Color obtained by mixing red and yellow"; Using a Namespace to Qualify Variable and Method Definitions | 311 It’s legal and common to use one qualifier namespace to qualify many different vari- ables, providing that each variable’s name is unique in that namespace (that’s the whole idea!). For example, here’s another variable, purple, also qualified by the namespace color: color var purple:String = "Color obtained by mixing red and blue"; When multiple variables and methods are qualified by the same namespace, n, those variables and methods can be thought of as forming a logical group. From a theoreti- cal point of view, then, it is natural to say that a variable or method “belongs to” or “is in” its declared namespace. However, on a technical level, an ActionScript “namespace” is not a data structure that physically contains variables or methods. Namespaces are not data containers, nor arrays. Namespaces serve only to qualify names. To avoid confusion, from now on, we’ll use the phrase “the namespace n qualifies the variable name p” rather than “the variable name p belongs to the namespace n” or “the variable name p is in the namespace n.” Namespaces do not contain names; they simply qualify them. Note that multiple namespaces cannot be specified for a single variable or method definition. Each definition can include a single qualifier namespace only. For exam- ple, this code is not legal: // Attempt to specify two namespaces for a single definition. fruit color var orange:String; // Yields the following error: // Only one namespace attribute // may be used per definition User-Defined Namespace Attributes in the Top-Level of a Class Only In the previous section we learned how to use our own namespaces as attributes for method and variable definitions. In fact, that’s the only place a user-defined namespace can legally be used as an attribute of a definition. User-defined namespaces can be used as attributes within the top level of a class definition only. If you attempt to use a user-defined namespace as an attribute of a definition any- where else, you’ll receive the following error: A user-defined namespace attribute can only be used at the top level of a class definition. 312 | Chapter 17: Namespaces Specifically, this means you cannot specify a user-defined namespace for the defini- tion of a class, package-level variable, package-level function, local variable, or nested function. The following definitions are illegal: // Illegal class definition. Namespace color not allowed here! color class Picker { } public function doSomething ( ):void { // Illegal local variable definition. Namespace color not allowed here! color var tempSwatch; } package p { // Illegal package-level variable definition. // Namespace color not allowed here! color var favorites:Array; } By contrast, namespaces built-in to ActionScript can be used as attributes of a defini- tion wherever ActionScript specifically allows it. For example, as we’ll learn in the later section “Namespaces for Access-Control Modifiers,” the access-control modifi- ers (public, internal, protected, and private) are built-in namespaces, and two of them (public and internal) can be used at the package level. Right, back to our code. We’ve now seen how to create two namespaces and three variables qualified by those namespaces, as follows: // Create two namespaces package kidsgame { namespace fruit = "http://www.example.com/games/kidsgame/fruit"; } package kidsgame { namespace color = "http://www.example.com/games/kidsgame/color"; } // Elsewhere, within a class, create three variables fruit var orange:String = "Round citrus fruit"; color var orange:String = "Color obtained by mixing red and yellow"; color var purple:String = "Color obtained by mixing red and blue"; Next we’ll learn how to refer to those variables using qualified identifiers. Qualified Identifiers So far all the identifiers we’ve encountered in this book have been so-called simple identifiers—identifiers with “simple” one-part names such as box, height, and border. But in order to work with namespaces we must use qualified identifiers. A qualified identifier is a special type of identifier that includes both a name and a namespace Qualified Identifiers | 313 that qualifies that name. Accordingly, qualified identifiers have two parts instead of just one: • The local name (the name that is unique within the specified namespace) • The qualifier namespace (the namespace within which the local name is unique) In ActionScript code, qualified identifiers are written as follows, qualifierNamespace::localName where the local name and qualifier namespace are joined together with the name- qualifier operator, written as two colons (::). The qualifierNamespace must be either a namespace identifier or a variable with a namespace as a value. We’ll learn about assigning namespaces to variables in the later section “Assigning and Passing Namespace Values.” The qualifierNamespace cannot, however, be a literal string that is the namespace name (URI). Let’s take a look at a real-life example of a qualified identifier. First, recall our earlier definition of the variable orange qualified by the namespace fruit: fruit var orange:String = "Round citrus fruit"; Here’s how we refer to that variable with a qualified identifier: fruit::orange Likewise, here’s the qualified identifier for the variable with the local name orange qualified by the namespace color: color::orange In the preceding examples, notice that the local names are the same (orange), but the qualifier namespace is different. ActionScript uses the qualifier namespace to distin- guish between the two local names. Qualified identifiers are used exactly like simple identifiers in ActionScript; they just happen to include a qualifier namespace. The format qualifierNamespace::localName applies to method and variable names alike: someNamespace::p // Access variable p someNamespace::m() // Invoke method m( ) A reference to a qualified identifier through an object uses the familiar dot operator, as follows: someObject.qualifierNamespace::localName For example, this code accesses someObj’s variable p, which is qualified by someNamespace: someObj.someNamespace::p And this code invokes someObj’s method m( ), which is qualified by someNamespace: someObj.someNamespace::m( ) 314 | Chapter 17: Namespaces Expanded Names We’ve just learned that a qualified identifier is a two-part name that includes a quali- fier namespace and a local name: qualifierNamespace::localName The qualifierNamespace in a qualified identifier is a reference to a Namespace object whose uri variable identifies the namespace name. An expanded name, by comparison, is a two-part name that includes a literal namespace name (URI) and a local name. Expanded names are used for documenta- tion purposes only, never in code, and are typically written in the following format: {namespaceName}localName For example, once again consider the definition of the namespace fruit: namespace fruit = "http://www.example.com/games/kidsgame/fruit"; And consider the definition of the variable orange, which is qualified by the namespace fruit: fruit var orange:String = "Round citrus fruit"; In code, we refer to that variable using the qualified identifier fruit::orange. How- ever, in documentation, we may wish to discuss that variable in reference to its actual namespace name, rather than its namespace identifier. We can do so using the following expanded name: {http://www.example.com/games/kidsgame/fruit}orange Expanded names are rarely used in this book, but are relatively common in the docu- mentation of XML namespaces. If you do not use XML namespaces, simply be aware that the syntax {namespaceName}localName is a documentation convention only, not a supported form of code. A Functional Namespace Example Let’s put our new knowledge of namespaces to work in a simplified program. Example 17-1 contains the beginnings of an application we’ll develop over the com- ing sections—a child’s word-recognition game. In the game, the player is shown a picture of either a color or a fruit, and asked to choose its name from a list of options. Figure 17-1 depicts two different screens in the game. For now, each item in the game will be represented by a variable whose value is a string description. We’ll define the catalog of all item variables in a class called Items.Using namespaces, we’ll separate the “fruit” variables from the “color” vari- ables; fruit variable names will be qualified by the fruit namespace, and color vari- able names will be qualified by the color namespace. A Functional Namespace Example | 315 Take a look at the code in Example 17-1, then we’ll review it. In the preceding code, we start by defining the game’s namespaces. We define the namespace fruit in the file fruit.as, as follows: package { namespace fruit = "http://www.example.com/games/kidsgame/fruit"; } Then we define the namespace color in the file color.as, as follows: package { namespace color = "http://www.example.com/games/kidsgame/color"; } We create each namespace at the package level so that it can be accessed by any class in the application. Figure 17-1. A child’s learn-to-read game Example 17-1. Kids Game: a functional namespace example // File fruit.as package { namespace fruit = "http://www.example.com/games/kidsgame/fruit"; } // File color.as package { namespace color = "http://www.example.com/games/kidsgame/color"; } // File Items.as package { public class Items { fruit var orange:String = "Round citrus fruit"; color var orange:String = "Color obtained by mixing red and yellow"; public function Items ( ) { trace(fruit::orange); trace(color::orange); } } } Orange Purple Orange Apple 316 | Chapter 17: Namespaces Next we define the class Items in the file Items.as.InItems, we define two variables, both of which have the local name orange. For the first variable, we specify the quali- fier namespace fruit; for the second we specify the qualifier namespace color. package { public class Items { fruit var orange:String = "Round citrus fruit"; color var orange:String = "Color obtained by mixing red and yellow"; } } Finally, to test that our code is working so far, in the Items constructor method we use the trace( ) function to display the value of both orange variables. To distinguish one orange variable from the other, we use the qualified identifiers fruit::orange and color::orange. package { public class Items { fruit var orange:String = "Round citrus fruit"; color var orange:String = "Color obtained by mixing red and yellow"; public function Items ( ) { trace(fruit::orange); // Displays: Round citrus fruit trace(color::orange); // Displays: Color obtained by // mixing red and yellow } } } Can you guess what would happen if we modified the preceding Items constructor to access the simple identifier orange, without including any qualifier namespace, as follows: public function Items ( ) { trace(orange); // What happens here? } The answer is that the following compile-time error occurs: Access of undefined property orange. The compiler cannot find a variable or method (i.e., a “property”) by the name orange because no variable or method with the simple identifier orange exists in the scope of the Items constructor method. The variables fruit::orange and color::orange are qualified by namespaces, so they are invisible to our attempt to reference them with an unqualified identifier. That said, in the later section “Open Namespaces and the use namespace Directive,” we’ll learn a shortcut for referring to qualified identifiers without including the qualifier namespace. Example 17-1 obviously doesn’t show a fully functional game, but it should give you a sense of basic namespace syntaxand usage. We’ll finish making our game later in this chapter. Namespace Accessibility | 317 At this early state of our game’s development, you might wonder if, rather than using namespaces, we should simply define variables with longer names, such as orangeFruit and orangeColor. Or you might wonder if we should separate the two kinds of “oranges” by assigning them to individual arrays, as in: var fruitList:Array = ["orange", "apple", "banana"]; var colorList:Array = ["orange", "red", "blue"]; Those are valid considerations. In fact, at our current level of simplicity, we would indeed be better served with arrays or longer variable names. But don’t lose faith in namespaces yet; we’re building towards more compelling usage scenarios. Namespace Accessibility Like variable and method definitions, namespace definitions can be modified by the access-control modifiers public, internal, protected, and private. Taken in combina- tion, the location of a namespace definition and the access-control modifier of that definition determine where the resulting namespace identifier can be used. Here are some general rules to help you decide where to define your namespaces: • When you need a namespace throughout a program or across a group of classes, define it at the package level. • When you need a namespace to define the visibility of variables and methods within a single class only, define it at the class level. • When you need a namespace only temporarily within a function, and you know the URI of the namespace, but you cannot access that namespace directly, define it at the function level. Let’s look at some examples, starting with namespaces defined at the package level. Accessibility of Package-Level Namespace Definitions In the following code, we define a namespace identifier, fruit, in the package kidsgame: package kidsgame { public namespace fruit = "http://www.example.com/games/kidsgame/fruit"; } Because fruit is declared at the package level with the access-control modifier public, it can be used to qualify any variable or method in the program. Of course, code out- side the kidsgame package would have to import the namespace fruit before using it, as in: package anyPackage { // Import the fruit namespace import kidsgame.fruit; 318 | Chapter 17: Namespaces public class AnyClass { // Ok to use fruit here now that it has been imported fruit var banana:String = "Long yellow fruit"; } } Now let’s set the accessibility of the namespace color to package-only using the access-control modifier internal: package kidsgame { internal namespace color = "http://www.example.com/games/kidsgame/color"; } When a namespace identifier is defined as internal at the package level, it can be used within the containing package only. The following code demonstrates. package kidsgame { public class Items { // Ok to use color here. This use of the color namespace is valid // it occurs within the kidsgame package color var green:String = "Color obtained by mixing blue and yellow"; } } package cardgame { import kidsgame.color; public class CardGame { // Illegal. // The color namespace can be used in the kidsgame package only. color var purple:String = "Color obtained by mixing blue and red"; } } Package-level namespaces can be defined as public or internal, but not private nor protected. Further, if the access modifier for a package-level namespace definition is omitted, then internal is assumed. For example, this code: package kidsgame { // Explicitly internal internal namespace fruit; } is the same as this code: package kidsgame { // Implicitly internal namespace fruit; } Next, let’s consider the class-level case. Accessibility of Class-Level Namespace Definitions A namespace identifier defined as private in a class is accessible throughout that class only, not in subclasses or any other external code: Namespace Accessibility | 319 public class A { private namespace n = "http://www.example.com/n"; // Fine. Namespace identifier n is accessible here. n var someVariable:int; } public class B extends A { // Error. Namespace identifier n is not accessible here. n var someOtherVariable:int; } We can use a private namespace in a class to implement a permission-based access control system, as discussed in the later section “Example: Permission-Based Access Control.” A namespace identifier defined as protected, internal,orpublic in a class is directly accessible throughout that class and its subclasses but not directly accessible in any other external code. Contrast this with a package-level public namespace definition, which creates a namespace identifier that can be accessed directly throughout a pro- gram. The following code demonstrates: public class A { public namespace n = "http://www.example.com/n"; // Fine. Namespace identifier n is directly accessible here. n var someVariable:int; } public class B extends A { // Fine. Namespace identifier n is directly accessible here. n var someOtherVariable:int; } public class C { // Error. Namespace identifier n is not directly accessible here. // (But n would be accessible if defined at the package level.) n var yetAnotherVariable:int; } Note that while a namespace identifier defined as internal or public in a class is directly accessible throughout that class and its subclasses only, the Namespace object referenced by the namespace identifier can still be accessed using static- variable syntax. For example, to access the Namespace object referenced by the namespace identifier n in the preceding code, we would use the expression: A.n. Static-variable style access to Namespace objects is subject to the normal restrictions imposed on protected, internal, and public static variables. For example, in the preceding code, because n is declared as public, the expression A.n is valid in any code that has access to the class A.Ifn were declared as internal, then the reference A.n would be valid within the 320 | Chapter 17: Namespaces containing package only. If n were declared as protected, then the reference A.n would be valid within the class A and its subclasses only. However, references to namespaces made through a class (such as A.n) cannot be used as an attribute of a variable or method definition. The following syntaxis ille- gal because an attribute of a variable or method definition must be a compile-time constant value: A.n var p:int; // Illegal. A.n is not a compile-time constant. So, if we can’t use A.n to qualify definitions, what can we use A.n for? Stay tuned, we’ll learn the answer to that question soon in the section “Assigning and Passing Namespace Values.” Now let’s consider one final namespace accessibility topic: namespace definitions in methods and functions. Accessibility of Function-Level Namespace Definitions Like other function-level definitions, a namespace identifier defined at the function level cannot take any access-control modifiers (i.e., cannot be defined as public, private, etc.) and is accessible in the scope of that function only: public function doSomething ( ):void { // This is illegal private namespace n = "http://www.example.com/n"; } Furthermore, local variables and nested function definitions cannot take namespaces as attributes: public function doSomething ( ):void { // This is also illegal namespace n = "http://www.example.com/n"; n var someLocalVariable:int = 10; } Hence, namespace identifiers defined in a function can be used to form qualified identifiers only. (The following code assumes that the namespace n has already been declared elsewhere and has been used to qualify the instance variable someVariable.) public function doSomething ( ):void { // This is legal namespace n = "http://www.example.com/n"; trace(n::someVariable); } Function-level namespace definitions are used only in the rare circumstance in which a namespace that is required temporarily by a function cannot be accessed directly but the namespace URI is known. For example, a function that processes an XML fragment containing qualified element names might use code such as the following: public function getPrice ( ):void { Qualified-Identifier Visibility | 321 namespace htmlNS = "http://www.w3.org/1999/xhtml"; output.text = htmlNS::table.htmlNS::tr[1].htmlNS::td[1].price; } For coverage of XML namespaces, see Chapter 18. Qualified-Identifier Visibility Perhaps you noticed that none of the qualified-identifier definitions in this book include any access-control modifiers (public, internal, protected,orprivate). We’ve seen plenty of this: fruit var orange:String = "Round citrus fruit"; But none of this (note the addition of the access-control modifier private): private fruit var orange:String = "Round citrus fruit"; And for good reason: it is illegal to use access-control modifiers with definitions that include a qualifier namespace. For example, the following code: private fruit var orange:String; yields the error: Access specifiers not allowed with namespace attributes But if access-control modifiers are illegal, then what governs the accessibility of a qualified identifier? Answer: the accessibility of the identifier’s qualifier namespace. The accessibility of the qualifier namespace in a qualified identifier determines that identifier’s accessibility. If the qualifier namespace is visible in a given scope, then the qualified identifier is also visible. For example, in the expression gameitems.fruit::orange, the variable fruit::orange is accessible if and only if the namespace fruit is accessible in the scope where the expression occurs. The accessibility of the variable fruit::orange is entirely deter- mined by the accessibility of the namespace fruit. Example 17-2 demonstrates qualified identifier visibility with generic code. Example 17-2. Qualified identifier visibility demonstration // Create namespace n, set to package-only visibility, in package one package one { internal namespace n = "http://www.example.com/n"; } // Create variable n::p in class A, package one package one { public class A { n var p:int = 1; } 322 | Chapter 17: Namespaces Comparing Qualified Identifiers Two namespaces are considered equal if, and only if, they have the same namespace name (URI). For example, to determine whether the namespaces in the qualified identifiers fruit::orange and color::orange are equal, ActionScript does not check whether the characters “fruit” in the first identifier match the characters “color” in second. Instead, ActionScript checks whether the Namespace instance referred to by fruit and the Namespace instance referred to by color have a matching uri variable value. If fruit.uri equals color.uri, then the namespaces are considered equal. Therefore, when we write the following comparison: trace(fruit::orange == color::orange); ActionScript performs this comparison (notice the use of expanded names, dis- cussed in the earlier section “Expanded Names”): {http://www.example.com/games/kidsgame/fruit}orange == {http://www.example.com/games/kidsgame/color}orange Hence, even though two qualified identifiers may look different on the surface, they might be the same, leading to perhaps surprising name conflicts. For example, in the following code, the attempted definition of the variable ns2::p is considered a } // Because namespace n's visibility is internal, the // variable n::p is accessible anywhere within the package one package one { public class B { public function B ( ) { var a:A = new A( ); trace(a.n::p); // OK } } } // But the variable n::p is not accessible to code outside of // package one package two { import one.*; public class C { public function C ( ) { var a:A = new A( ); trace(a.n::p); // Illegal because n is internal to package one, and // is, therefore, not accessible within package two } } } Example 17-2. Qualified identifier visibility demonstration (continued) Assigning and Passing Namespace Values | 323 compile-time error because a variable with the expanded name {http://www.example. com/general}p already exists: namespace ns1 = "http://www.example.com/general" namespace ns2 = "http://www.example.com/general" ns1 var p:int = 1; ns2 var p:int = 2; // Error! Duplicate variable definition! Even though the identifiers ns1 and ns2 are different, the variables ns1::p and ns2::p are considered identical because they have the same expanded name ({http://www. example.com/general}p). Note that namespace names (URIs) are compared as strings, and case sensitivity mat- ters. So, whereas two URIs that differ in case only would be considered the same by a web browser, ActionScript considers them different. The following two URIs are considered different by ActionScript because “example” is not capitalized in the first but is capitalized in the second: namespace ns1 = "http://www.example.com" namespace ns2 = "http://www.Example.com" trace(ns1 == ns2); // Displays: false Assigning and Passing Namespace Values Because every namespace is represented by an instance of the Namespace class, namespaces can be assigned to variables or array elements, passed to methods, returned from methods, and generally used like any other object. This flexibility lets us: • Pass a namespace from one scope to another • Choose between multiple namespaces dynamically at runtime These activities are critical to namespace usage in ActionScript. Let’s see how. Assigning a Namespace Value to a Variable To assign a namespace value to a variable, we use the same assignment syntaxwe’d use with any other value. For example, the following code assigns the namespace value in fruit to the variable currentItemType (recall that a namespace value is a Namespace object): // File fruit.as package { namespace fruit = "http://www.example.com/games/kidsgame/fruit"; } // File Items.as package { public class Items { // Assign the value in fruit to currentItemType 324 | Chapter 17: Namespaces private var currentItemType:Namespace = fruit; } } A variable referencing a Namespace object can be used to form a qualified identifier. For example, consider the following variable definition: fruit var orange:String = "Round citrus fruit"; To refer to that variable, we can use either the expression fruit::orange or the expression currentItemType::orange. By changing the value of currentItemType to some other namespace, we can dynamically adjust the meaning of the identifier currentItemType::orange, and of all other methods and variables qualified by currentItemType across an entire program. If we organize groups of methods and variables with namespaces, we can use dynamic namespace selection to switch between operational modes in the program. For example, suppose we’re writing an instant messaging application with two modes represented by corresponding namespaces, offline and online. The applica- tion defines two versions of a method named sendMessage( )—one to use in online mode and one to use in offline mode. online sendMessage (msg:String):void { // Send message now... } offline sendMessage (msg:String):void { // Queue message and send later... } The application keeps track of the currently active mode using a variable, currentMode. Whenever the server connection is established or lost, currentMode is updated. private function connectListener (e:Event):void { currentMode = online; } private function closeListener (e:Event):void { currentMode = offline; } All calls to sendMessage( ) use currentMode as a qualifier namespace, as shown in the following code: currentMode::sendMessage("yo dude"); By updating the currentMode variable, the application dynamically switches between the two versions of sendMessage( ), depending on the connection status. In the later section “Example: Program Modes,” we’ll revisit the concept of namespaces as modes in a Japanese/English dictionary example that switches between different search modes. Assigning and Passing Namespace Values | 325 Just remember that while a variable can be used to specify the namespace of a quali- fied identifier, variables cannot be used to specify the namespace for a variable or method definition. The third line of the following code: namespace fruit; var currentItemType:Namespace = fruit; currentItemType var orange:String = "Round citrus fruit"; yields this error: Namespace was not found or is not a compile-time constant. Similarly, variables cannot be used to specify the namespace in a use namespace directive. We’ll cover the use namespace directive in the later section “Open Namespaces and the use namespace Directive.” Namespaces as Method Arguments and Return Values In addition to being assigned to variables and array elements, namespace values can be passed to and returned from methods. For example, the following code defines a method, doSomething( ), that accepts a namespace value as an argument: public function doSomething (n:Namespace):void { trace(n); } This code passes the namespace fruit to the method doSomething( ): doSomething(fruit); A namespace might be passed to a method in order to send one part of a program’s context to another. For example, a shopping cart application might pass the namespace currentLocale to a Checkout class that would then dynamically select the appropriate currency and time-sensitive greeting based on the user’s location. This code defines a method, getNamespace( ) that returns the namespace fruit: public function getNamespace ( ):Namespace { return fruit; } A namespace might be returned from a method in order to grant the caller privileged access to restricted variables and methods. For a full example of returning a namespace from a method as part of permission-based access control, see the section “Example: Permission-Based Access Control,” later in this chapter. A Namespace Value Example Now that we’ve studied how namespace values work in theory, let’s revisit our ear- lier child’s game example to see how namespace values can be used in an actual application. Recall that the game is a reading exercise in which the player tries to identify a randomly chosen color or fruit. The first version of the game code (Example 17-1) showed a single, extremely simplified section of the game. In this 326 | Chapter 17: Namespaces updated version, we’ll make the game fully functional, providing a deeper look at how namespaces help manage multiple sets of data. Skim the code in Example 17-3 for familiarity. In the example, namespaces are used within the Items and KidsGame classes only, so you should focus most of your attention on those classes. For information on the techniques used to generate the user interface in the example, see Part II. A detailed analysis follows the example listing. Example 17-3. Kids Game: a namespace value example // File fruit.as package { public namespace fruit = "http://www.example.com/games/kidsgame/fruit"; } // File color.as package { public namespace color = "http://www.example.com/games/kidsgame/color"; } // File Items.as package { // A simple data-storage class containing the Item objects for the game. public class Items { // The fruits fruit var orange:Item = new Item("Orange", "fruit-orange.jpg", 1); fruit var apple:Item = new Item("Apple", "fruit-apple.jpg", 2); // The colors color var orange:Item = new Item("Orange", "color-orange.jpg", 3); color var purple:Item = new Item("Purple", "color-purple.jpg", 4); // Arrays that track complete sets of items (i.e., all the fruits, or // all the colors) fruit var itemSet:Array = [fruit::orange, fruit::apple]; color var itemSet:Array = [color::orange, color::purple]; // An array of namespaces representing the types of the item // sets in the game private var itemTypes:Array = [color, fruit]; // Returns all the fruit items in the game fruit function getItems ( ):Array { return fruit::itemSet.slice(0); } // Returns all the color items in the game color function getItems ( ):Array { return color::itemSet.slice(0); } // Returns the list of available item types in the game public function getItemTypes ( ):Array { Assigning and Passing Namespace Values | 327 return itemTypes.slice(0); } } } // File KidsGame.as package { import flash.display.*; import flash.events.*; import flash.utils.*; // The main application class for a child's learn-to-read game // demonstrating the basic usage of namespaces in ActionScript. // The player is shown a picture of a color or a fruit, and asked to // choose its name from a list of options. public class KidsGame extends Sprite { private var gameItems:Items; // The list of all items in the game private var thisQuestionItem:Item; // The item for each question private var questionScreen:QuestionScreen; // The user interface // Constructor public function KidsGame( ) { // Retrieve the game items (the fruits and colors which the user must // name) gameItems = new Items( ); // Display the first question newQuestion( ); } // Creates and displays a new random game question public function newQuestion ( ):void { // Get the full list of item types (an array of namespaces) var itemTypes:Array = gameItems.getItemTypes( ); // Pick a random item type (one of the namespaces in itemTypes) var randomItemType:Namespace = itemTypes[Math.floor( Math.random( )*itemTypes.length)]; // Retrieve the randomly chosen item set var items:Array = gameItems.randomItemType::getItems( ); // Randomly pick the item for this question from the item set thisQuestionItem = items[Math.floor(Math.random( )*items.length)]; // Remove the previous question, if there was one if (questionScreen != null) { removeChild(questionScreen); } // Display the new question questionScreen = new QuestionScreen(this, items, thisQuestionItem); addChild(questionScreen); } Example 17-3. Kids Game: a namespace value example (continued) 328 | Chapter 17: Namespaces // Handles a player's guess public function submitGuess (guess:int):void { trace("Guess: " + guess + ", Correct: " + thisQuestionItem.id); if (guess == thisQuestionItem.id) { questionScreen.displayResult("Correct!"); // Disable the answer buttons while the // player waits for the next question. questionScreen.disable( ); // Wait 3 seconds then show another question. var timer:Timer = new Timer(3000, 1); timer.addEventListener(TimerEvent.TIMER, doneResultDelay); timer.start( ); } else { questionScreen.displayResult("Incorrect. Please try again."); } } // Makes a new question after the previous question is finished. private function doneResultDelay (e:TimerEvent):void { newQuestion( ); } } } // File Item.as package { // A simple data container that tracks an item's information. public class Item { // The item's name (for example, "apple") public var name:String; // The URL from which to load an image representing the item public var src:String; // A unique identifier for the item, used to evaluate player guesses public var id:int; // Constructor public function Item (name:String, src:String, id:int) { this.name = name; this.src = src; this.id = id; } } } // File QuestionScreen.as package { import flash.events.*; import flash.display.*; import flash.text.*; import flash.net.*; Example 17-3. Kids Game: a namespace value example (continued) Assigning and Passing Namespace Values | 329 // Creates the user interface for a question public class QuestionScreen extends Sprite { private var status:TextField; private var game:KidsGame; private var items:Array; private var thisQuestionItem:Item; // Constructor public function QuestionScreen (game:KidsGame, items:Array, thisQuestionItem:Item) { // Keep a reference to the main game engine this.game = game; // Assemble question data this.items = items; this.thisQuestionItem = thisQuestionItem; // Put the question on screen makeQuestion( ); } // Creates and displays a question's interface public function makeQuestion ( ):void { // Display the graphic for the item var imgLoader:Loader = new Loader( ); addChild(imgLoader); imgLoader.load(new URLRequest(thisQuestionItem.src)); // Add a selection of clickable words for the player to choose // from. For the sake of simplicity, we'll display the name of every // item in the item set. var wordButton:WordButton; for (var i:int = 0; i < items.length; i++) { wordButton = new WordButton( ); wordButton.setButtonText(items[i].name); wordButton.setID(items[i].id); wordButton.y = 110 + i*(wordButton.height + 5); wordButton.addEventListener(MouseEvent.CLICK, clickListener); addChild(wordButton); } // Create a text field in which to display question status status = new TextField( ); status.autoSize = TextFieldAutoSize.LEFT; status.y = wordButton.y + wordButton.height + 10; status.selectable = false; addChild(status); } // Displays a message in the status field public function displayResult (msg:String):void { Example 17-3. Kids Game: a namespace value example (continued) 330 | Chapter 17: Namespaces status.text = msg; } // Displays user input for this question public function disable ( ):void { // Disables mouse events for all children of this Sprite. mouseChildren = false; } // Responds to the clicking of a word button private function clickListener (e:MouseEvent):void { // The player's guess is the item id associated with // the WordButton object, as set in makeQuestion( ). game.submitGuess(e.target.getID( )); } } } // File WordButton.as package { import flash.text.*; import flash.display.*; // Represents a clickable word on screen (i.e., an available choice for // a question). The ID indicates the item id of the player's // guess (see Item.id). public class WordButton extends Sprite { private var id:int; // The ID of the item this button represents private var t:TextField; // Constructor public function WordButton ( ) { t = new TextField( ); t.autoSize = TextFieldAutoSize.LEFT; t.border = true; t.background = true; t.selectable = false; addChild(t); buttonMode = true; mouseChildren = false; } // Assigns the text to display on the button public function setButtonText (text:String):void { t.text = text; } // Assigns the ID of the item this button represents public function setID (newID:int):void { id = newID; } Example 17-3. Kids Game: a namespace value example (continued) Assigning and Passing Namespace Values | 331 Done skimming the code? Great, let’s examine it in more detail. You probably noticed that the namespace definitions in the game haven’t changed at all since Example 17-1. However, the Items class has changed substantially and there are sev- eral new classes, including: • KidsGame—the main application class • Item—provides information about a particular game item • QuestionScreen—builds the each question’s user interface • WordButton—represents a clickable word on screen Because our present focus is namespaces, we’ll examine the Items and KidsGame classes only; study of the remaining classes is left as an exercise for the reader. Let’s start by looking at how Items has been updated since Example 17-1. First, we’ve added two new item variables, fruit::apple and color::purple. These new variables give the fruit and color item categories a total of two items each—orange and apple for the fruits, and orange and purple for the colors. We’ve also replaced Example 17-1’s simple item descriptions (such as “Round citrus fruit”) with instances of the Item class. The Item instances track the item’s name, image URL, and ID. The following code shows the updated item variables. As in Example 17-1, each variable is qualified by a namespace corresponding to the variety of item. fruit var orange:Item = new Item("Orange", "fruit-orange.jpg", 1); fruit var apple:Item = new Item("Apple", "fruit-apple.jpg", 2); color var orange:Item = new Item("Orange", "color-orange.jpg", 3); color var purple:Item = new Item("Purple", "color-purple.jpg", 4); Next, the Items class also adds two arrays to manage the items as groups. Each array maintains a complete list of its group’s items (either the fruits or the colors). The arrays are assigned to variables with the same local name (itemSet) but qualified by different namespaces (fruit and color). fruit var itemSet:Array = [fruit::orange, fruit::apple]; color var itemSet:Array = [color::orange, color::purple]; To give other classes access to the different item sets in the game, Items defines two methods with the same local name, getItems( ), but qualified by different namespaces, fruit and color. Each getItems( ) method returns a copy of the item set that corresponds to its namespace. Hence, the appropriate item-set can be accessed dynamically based on the current question type (either color or fruit). // Returns the ID of the item this button represents public function getID ( ):int { return id; } } } Example 17-3. Kids Game: a namespace value example (continued) 332 | Chapter 17: Namespaces fruit function getItems ( ):Array { // Return the fruits. return fruit::itemSet.slice(0); } color function getItems ( ):Array { // Return the colors. return color::itemSet.slice(0); } Finally, Items defines the variable itemTypes and a corresponding accessor method getItemTypes( ). The itemTypes variable maintains a list of all the different varieties of items in the game. Our game has only two varieties—fruit and color—but more could easily be added. Each item variety corresponds to a namespace, so itemTypes is an array of namespaces. The getItemTypes( ) method returns a copy of that array, giv- ing external code a central location from which to obtain the official list of item types in the game. // The itemTypes variable private var itemTypes:Array = [color, fruit]; // The getItemTypes( ) method public function getItemTypes ( ):Array { return itemTypes.slice(0); } That’s it for the changes to Items. Now let’s turn to the new main application class, KidsGame. In contrast with the Items class, KidsGame never uses the namespace identifiers fruit and color directly. Instead, it refers to those namespaces via the Items class’s instance method getItemTypes( ). The KidsGame class’s gameItems variable provides KidsGame with access to the game data, in the form of an Items object. Meanwhile, the KidsGame class’s newQuestion( ) method generates a new question based on the data in gameItems. The newQuestion( ) method contains the majority of the namespace code we’re interested in, so let’s look at it line-by-line. Recall that each question displays an item from one of the predetermined item sets maintained by the Items class (fruit::itemSet or color::itemSet). Accordingly, the first task in newQuestion( ) is to randomly choose the item set for the question being generated. We start by retrieving the entire array of possible item sets (i.e., namespaces) from the Items class, using gameItems.getItemTypes( ): var itemTypes:Array = gameItems.getItemTypes( ); Then we randomly choose a namespace from the resulting array. For convenience, we assign the chosen namespace to a local variable, randomItemType. var randomItemType:Namespace = itemTypes[Math.floor( Math.random( )*itemTypes.length)]; Notice that randomItemType’s datatype is Namespace because it refers to a namespace value. Once an item set (namespace) for the question has been chosen, we must Assigning and Passing Namespace Values | 333 retrieve the array of actual items in that set. To retrieve the appropriate array of items (either fruits or colors), we invoke the method that corresponds to our chosen namespace—either Items’s fruit::getItems( ) or Items’s color::getItems( ). But instead of referring to the method we want directly, we dynamically generate the method’s qualified identifier using the randomItemType variable to specify the namespace, like this: gameItems.randomItemType::getItems( ) The returned array is assigned to a local variable, items: var items:Array = gameItems.randomItemType::getItems( ); In the preceding method call, notice that the behavior of the program is determined by the context of the program. This can be thought of as a kind of polymorphism, one based not on class inheritance but on the arbitrary groups of methods and variables delineated by namespaces. With our array of items cheerfully in hand, we can get on with the everyday work of putting a question on screen. First we randomly pick the item to display from among the array of items: thisQuestionItem = items[Math.floor(Math.random( )*items.length)]; Then we put the item image and text choices for the chosen item on screen using the QuestionScreen class: // Remove the previous question, if there was one if (questionScreen != null) { removeChild(questionScreen); } // Display the new question questionScreen = new QuestionScreen(this, items, thisQuestionItem); addChild(questionScreen); Here’s another look at the newQuestion( ) method. Pay special attention to its use of namespace values as you review it one last time. public function newQuestion ( ):void { // Get the full list of item types (an array of namespaces) var itemTypes:Array = gameItems.getItemTypes( ); // Pick a random item type (one of the namespaces in itemTypes) var randomItemType:Namespace = itemTypes[Math.floor( Math.random( )*itemTypes.length)]; // Retrieve the randomly chosen item set var items:Array = gameItems.randomItemType::getItems( ); // Randomly pick the item for this question from the item set thisQuestionItem = items[Math.floor(Math.random( )*items.length)]; // Remove the previous question, if there was one 334 | Chapter 17: Namespaces if (questionScreen != null) { removeChild(questionScreen); } // Display the new question questionScreen = new QuestionScreen(this, items, thisQuestionItem); addChild(questionScreen); } The remainder of the code in Example 17-3 relates to game logic and user interface creation, which are not our present focus. As mentioned earlier, you should study the rest of the code on your own. For information on user interface coding tech- niques, see Part II of this book. Well that was a nice, practical example. And there are more examples coming, but first we have to cover two more fundamental namespace concepts: open namespaces and namespaces for access-control modifiers. Open Namespaces and the use namespace Directive Remember the simple Items class from Example 17-1? package { public class Items { fruit var orange:String = "Round citrus fruit"; color var orange:String = "Color obtained by mixing red and yellow"; public function Items ( ) { trace(fruit::orange); trace(color::orange); } } } As we learned earlier, one way to access the orange variables in the preceding code is to use qualified identifiers, as in: trace(fruit::orange); // Displays: Round citrus fruit trace(color::orange); // Displays: Color obtained by // mixing red and yellow But, for the sake of convenience, ActionScript also provides another tool for access- ing variables qualified by a namespace: the use namespace directive. The use namespace directive adds a given namespace to the so-called open namespaces for a particular scope of a program. The open namespaces is the set of namespaces Action- Script consults when attempting to resolve unqualified references. For example, if namespace n is in the open namespaces, and ActionScript encounters an unqualified reference to a variable p, then ActionScript will automatically check for the existence of n::p. Here’s the general form of the use namespace directive: Open Namespaces and the use namespace Directive | 335 use namespace namespaceIdentifier where namespaceIdentifier is the namespace identifier that should be added to the set of open namespaces. Note that namespaceIdentifier must be a compile-time con- stant, so it cannot be a variable that references a namespace value. Let’s see how use namespace works by referring directly to the local name orange after adding the namespace fruit to the set of open namespaces in the preceding Items constructor (this is also described as “opening the namespace fruit”). public function Items ( ) { use namespace fruit; trace(orange); } Because we added fruit to the open namespaces, when ActionScript encounters the code: trace(orange); it automatically checks to see if the qualified identifier fruit::orange exists. In our example, that identifier does exist, so it is used in place of orange. In other words, in the Items constructor, this code: trace(fruit::orange); // Displays: Round citrus fruit has the same result as this code: use namespace fruit; trace(orange); // Displays: Round citrus fruit Open Namespaces and Scope Each scope of an ActionScript program maintains a separate list of open namespaces. A namespace opened in a given scope will be open for that entire scope, including nested scopes but will not be open in other scopes. The opened namespace is avail- able even prior to the occurrence of the use namespace statement (however, the best practice is to place the use namespace directive at the top of the enclosing code block). Recall that “scope” means “region of a program.” In ActionScript, a unique scope is defined for each package, class, and method. Condi- tionals and loops do not have their own scope. Example 17-4 uses generic code to demonstrate two separate scopes and their sepa- rate list of open namespaces. Comments will guide you through the code. Example 17-4. Open namespace demo public class ScopeDemo { // Create a namespace. private namespace n1 = "http://www.example.com/n1"; 336 | Chapter 17: Namespaces Because an open namespace remains open in nested scopes, we can open a namespace at the class or package level in order to use it throughout the entire class or package statement. Note, however, that once a namespace is opened, it cannot be “closed.” There is no “unuse namespace” directive, and no way to remove a namespace from the open namespaces in a particular scope. // Create two variables qualfied by the namespace n1. n1 var a:String = "a"; n1 var b:String = "b"; // Constructor public function ScopeDemo ( ) { // Call a method that accesses the variable n1::a. showA( ); } public function showA ( ):void { // This unqualified reference a matches the fully qualified // identifier n1::a because the following line opens the namespace n1. trace(a); // OK! // Open namespace n1. use namespace n1; // Unqualified reference a again matches n1::a. trace(a); // OK! // Create a nested function. function f ( ):void { // The namespace n1 is still open in nested scopes... trace(a); // OK! Matches n1::a. } // Call the nested function. f( ); } public function showB ( ):void { // The following code makes a misguided attempt to access n1::b. // The namespace n1 is open in the scope of showA() only, not showB( ), // so the attempt fails. Furthermore, no variable with the simple // identifier b exists in the scope of showB( ), so the compiler // generates the following error: // Attempted access of inaccessible property b through a reference // with static type ScopeDemo. trace(b); // ERROR! } } Example 17-4. Open namespace demo (continued) Open Namespaces and the use namespace Directive | 337 Opening Multiple Namespaces It’s perfectly legal to open multiple namespaces in the same scope. For example, here are four variables divided into two namespaces (the variables are excerpted from the Items class in Example 17-3): fruit var orange:Item = new Item("Orange", "fruit-orange.jpg", 1); fruit var apple:Item = new Item("Apple", "fruit-apple.jpg", 2); color var orange:Item = new Item("Orange", "color-orange.jpg", 3); color var purple:Item = new Item("Purple", "color-purple.jpg", 4); Suppose we add a method to the Items class, showItems( ), to display all game items. In showItems( ), we can open both the fruit and color namespaces, and then refer to fruit::apple and color::purple without specifying a qualifier namespace: public function showItems ( ):void { use namespace fruit; use namespace color; // Look mom! No namespaces! trace(apple.name); // Displays: Apple trace(purple.name); // Displays: Purple } Let’s consider how this works. Earlier we learned that open namespaces means “the set of namespaces ActionScript consults when attempting to resolve unqualified refer- ences.” If multiple namespaces are open in a given scope, then ActionScript examines them all for each and every unqualified reference in that scope. For example, in showItems( ), the fruit and color namespaces are both open. Therefore, when Action- Script encounters the unqualified reference apple it looks for both fruit::apple and color::apple.Inapple’s case, the unqualified reference matches fruit::apple but does not match color::apple. Because apple matches only one qualified identifier (namely, fruit::apple), that qualified identifier is used in place of the unqualified reference, apple. But what happens if we use an unqualified reference, such as orange, that matches two qualified identifiers: public function showItems ( ):void { use namespace fruit; use namespace color; // Matches fruit::orange and color::orange--what happens here? trace(orange); } When an unqualified reference matches a name in more than one open namespace, a runtime error occurs. The preceding code yields the following error: Ambiguous reference to orange. 338 | Chapter 17: Namespaces Due to a bug in some Adobe ActionScript compilers, the preceding error might go unreported. If we open both the fruit and color namespaces, then we must use the qualified identifiers fruit::orange or color::orange to refer to our orange variables unambigu- ously, as follows: public function showItems ( ):void { use namespace fruit; use namespace color; trace(apple); // Displays: Apple trace(purple); // Displays: Purple // Both fruit and color are open so references to orange // must be fully qualified. trace(fruit::orange); trace(color::orange); } Namespaces for Access-Control Modifiers Just as we use namespaces to control variable and method visibility in our own pro- grams, so ActionScript uses namespaces to control the visibility of every variable and method in every program! Remember the four access-control modifiers in Action- Script—public, internal, protected, private? ActionScript, itself, enforces those visibil- ity rules using namespaces. For example, from ActionScript’s perspective, the variable definition: class A { private var p:int; } means “create a new variable p qualified by the class A’s private namespace.” In each scope, ActionScript implicitly opens the appropriate namespaces for the vari- ous access-control modifiers. For example, in every scope ActionScript always adds the global public namespace to the set of open namespaces. At the top level of a package, ActionScript also adds that package’s internal and public namespaces. In code within a class that resides in a package, ActionScript also adds the class’s private and protected namespaces. The set of open namespaces, then, includes not just user-opened namespaces, but also the access-control namespaces that are implic- itly opened by ActionScript in each scope. You cannot use the use namespace directive to open one of the access- control namespaces explicitly. ActionScript opens the access-control namespaces automatically according to the current scope. Namespaces for Access-Control Modifiers | 339 The access-control modifier namespaces determine the accessibility of identifiers and prevent naming conflicts. For example, in the following code, a superclass, Parent, and a subclass, Child, each define a private variable with the same name: description. The Parent class’s description variable is not accessible to code within the Child class because description is qualified by the Parent class’s private namespace, which is not open in the scope of the Child class. As a result, the vari- able names do not conflict. package p { public class Parent { private var description:String = "A Parent object"; public function Parent ( ) { trace(description); } } } package p { public class Child extends Parent { private var description:String = "A Child object"; public function Child ( ) { trace(description); // No conflict } } } But if we change the access modifier for the Parent class’s description variable to protected, a conflict arises. Let’s consider exactly why. First let’s change the access modifier for description to protected: public class Parent { protected var description:String = "A Parent object"; } Now let’s pretend we’re ActionScript attempting to run the code in the Child class constructor. We enter the constructor and encounter a reference to the identifier description. In order to resolve that identifier, we must check for it in the open namespaces. And what are the open namespaces in the Child class constructor? As we just learned, in code within a class that resides in a package, ActionScript opens the class’s private and protected namespaces, the package’s internal and public namespaces, and the global public namespace. So the open namespaces are: • The Child class’s private namespace • The Child class’s protected namespace (which qualifies all members inherited from the direct superclass) • The package p’s internal namespace • The package p’s public namespace • The global public namespace • All explicitly opened custom namespaces 340 | Chapter 17: Namespaces When ActionScript checks for description in the open namespaces, it finds two matches: Child’s private::description and Child’s protected::description.Aswe learned in the previous section, when an unqualified reference matches a name in more than one open namespace, an ambiguous-reference error occurs. Furthermore, when multiple names are qualified by different implicitly opened namespaces, a defi- nition-conflict error occurs. In the case of description, the specific conflict error is: A conflict exists with inherited definition Parent.description in namespace protected. If you create conflicting method and variable names in your code, ActionScript will describe the conflict in relation to the namespace where the conflict occurred. For example, the following code: package { import flash.display.*; public class SomeClass extends Sprite { private var prop:int; private var prop:int; // Illegal duplicate property definition } } yields the following error: A conflict exists with definition prop in namespace private. (Actually, due to a compiler bug in FlexBuilder 2 and Flash CS3, the preceding mes- sage erroneously reads “namespace internal” whereas it should read “namespace private.”) Likewise, the following code: package { import flash.display.*; public class SomeClass extends Sprite { private var x; } } yields the following error (because—as we can learn by reading Adobe’s Action- Script Language Reference—DisplayObject already defines the public variable x): A conflict exists with inherited definition flash.display:DisplayObject.x in namespace public. Import Opens Public Namespaces Note that technically, importing a package, as in: import somePackage.*; opens the public namespace of the imported package. However, it does not open the internal namespace of the imported package. Even when a package is imported, its internal identifiers remain inaccessible to outside code. Applied Namespace Examples | 341 Applied Namespace Examples This chapter’s introduction cited four practical scenarios for namespace use: • Prevent naming conflicts • Framework-level member visibility • Permission-based access control • Program modes In the preceding section we learned how namespaces prevent naming conflicts. In this section we’ll explore each of the remaining three scenarios with a real-world example. Example: Framework-Internal Visibility Our first applied namespace example comes from Adobe’s Flex framework, a library of user interface components and utilities for rich Internet application development. The Flexframework contains a lot of code—hundreds of classes in dozens of pack- ages. Some methods and variables in those classes must be accessible across differ- ent packages but are still considered internal to the overall framework. This presents a dilemma: if the methods and variables are declared public, then code outside the framework will have unwanted access to them, but if they are declared internal, they cannot be shared across packages. To address this issue, the Flexframework defines the namespace mx_internal, and uses it to qualify methods and variables that should not be used outside the frame- work but must be accessible across different packages within the framework. Here’s the declaration of the mx_internal namespace: package mx.core { public namespace mx_internal = "http://www.adobe.com/2006/flex/mx/internal"; } Let’s look at a specific mx_internal example from the Flex framework. To work with grids of data, such as would be required in a spreadsheet applica- tion, the Flexframework provides the DataGrid component. The DataGrid class resides in the mx.controls package. Helper classes for DataGrid live in a separate package: mx.controls.gridclasses. To make communication as efficient as possi- ble between the DataGrid and its helper classes, DataGrid accesses some of its helper classes’ internal variables directly rather than via publicly accessible getter methods. These internal variables, however, should not be used by classes outside the Flexframework, so they are qualified by the mx_internal namespace. For example, the helper class mx.controls.gridclasses.DataGridColumn tracks the index of a column in the variable mx_internal::colNum. 342 | Chapter 17: Namespaces // File DataGridColumn.as mx_internal var colNum:Number; To retrieve the column index, the DataGrid class first opens the mx_internal namespace: use namespace mx_internal; and then accesses mx_internal::colNum directly, as shown in this setter method excerpt: // File DataGrid.as public function set columns(value:Array):void { // Initialise "colNum" on all columns var n:int = value.length; for (var i:int = 0; i < n; i++) { var column:DataGridColumn = _columns[i]; column.owner = this; // Access mx_internal::colNum directly. (Remember that the // mx_internal namespace is open, so column.colNum is equivalent // to column.mx_internal::colNum.) column.colNum = i; } // Remainder of method not shown } Classes outside the framework use the public method getColumnIndex( ) to retrieve the column index instead of accessing mx_internal::colNum directly. Intent is 9/10 of the law. Placing variables or methods in the mx_internal namespace certainly reduces their immediate visibility, but it does not technically restrict code outside the Flexframework from accessing them. Any developer who knows the URI of the mx_internal namespace can use it to access any of the variables or methods qualified by mx_internal. The goal of mx_internal, however, is not to technically secure variables and methods against developer use. Rather, it is to erect a bold warning sign indicating that the variables and methods are not for external use and might change without warning or cause erratic behavior if accessed by code outside the Flex framework. Example: Permission-Based Access Control Our second namespace example demonstrates a custom form of access control where a class defines a group of methods and variables that only designated classes can access. Here are the participants in this example: The sheltered class The class that grants access to its restricted methods and variables The restricted methods and variables The group of methods and variables to which access is limited Applied Namespace Examples | 343 The authorized classes Classes that are granted access to the restricted methods and variables Here’s the basic code for the sheltered class: package { // This is the sheltered class. public class ShelteredClass { // The namespace restricted qualifies variables and // methods to which access is restricted. private namespace restricted; // This is the array of authorized classes. In this // example there is only one authorized class: Caller. private var authorizedClasses:Array = [ Caller ]; // This is a restricted variable. // It can be accessed by authorized classes only. restricted var secretData:String = "No peeking"; // This is a restricted method. // It can be accessed by authorized classes only. restricted function secretMethod ( ):void { trace("Restricted method secretMethod( ) called"); } } } The sheltered class keeps an array of the authorized classes. It also defines a private namespace to qualify its restricted methods and variables. The namespace is private so that other classes cannot access it directly. Additionally, the URI for the namespace is automatically generated so that it cannot be discovered and used out- side the class. Finally, the sheltered class defines the restricted variables and meth- ods themselves. To access a restricted method or variable (e.g., secretData or secretMethod( )), a pro- spective class must obtain the proverbial keys to the front door. That is, it must retrieve a reference to the namespace that qualifies the restricted methods and vari- ables. But the sheltered class will grant that reference only if the prospective class— lets call it the “caller class”—is in the authorizedClasses array. In our example, the caller class will ask ShelteredClass for a reference to the restricted namespace using ShelteredClass’s instance method getRestrictedNamespace( ). The getRestrictedNamespace( ) method accepts an instance of the caller class as an argu- ment. If the caller instance is authorized, getRestrictedNamespace( ) returns a reference to the restricted namespace. Otherwise, getRestrictedNamespace( ) returns null, indi- cating to the caller that access to the restricted methods and variables is denied. Here’s the code for the getRestrictedNamespace( ) method: 344 | Chapter 17: Namespaces public function getRestrictedNamespace (callerObject:Object):Namespace { // Check to see if the callerObject is in the authorizedClasses array. for each (var authorizedClass:Class in authorizedClasses) { // If the caller object is an instance of an authorized class... if (callerObject is authorizedClass) { // ...pass back a reference to the restricted namespace (the // keys to the front door) return restricted; } } // The caller object is not an instance of // an authorized class, so abort return null; } Example 17-5 shows the code for ShelteredClass in its entirety, complete with the getRestrictedNamespace( ) method. Example 17-5. The ShelteredClass class package { // This is the sheltered class public class ShelteredClass { // The namespace restricted qualifies variables and // methods to which access is restricted private namespace restricted; // This is the array of authorized classes. In this // example there is only one authorized class: Caller. private var authorizedClasses:Array = [ Caller ]; // This is a restricted variable. // It can be accessed by authorized classes only restricted var secretData:String = "No peeking"; // This is a restricted method. // It can be accessed by authorized classes only restricted function secretMethod ( ):void { trace("Restricted method secretMethod( ) called"); } public function getRestrictedNamespace (callerObject:Object):Namespace { // Check to see if the callerObject is in the authorizedClasses array. for each (var authorizedClass:Class in authorizedClasses) { // If the caller object is an instance of an authorized class... if (callerObject is authorizedClass) { // ...pass back a reference to the restricted namespace (the // keys to the front door) return restricted; } } // The caller object is not an instance of // an authorized class, so abort Applied Namespace Examples | 345 Now let’s look at Caller, a class that wishes to access ShelteredClass’s restricted methods and variables. Having already seen the authorizedClasses array in ShelteredClass, we know that Caller is a legal class. In our example, Caller is also the main application class, so it extends Sprite. The Caller class creates an instance of ShelteredClass in its constructor method and assigns that instance to the variable shelteredObject. package { import flash.display.*; public class Caller extends Sprite { private var shelteredObject:ShelteredClass; public function Caller ( ) { shelteredObject = new ShelteredClass( ); } } } To invoke secretMethod( ) on ShelteredClass,aCaller object must first retrieve a refer- ence to the restricted namespace. To do so, the Caller object passes itself to getRestrictedNamespace( ) and assigns the result (either restricted or null) to a vari- able, key, for later use. var key:Namespace = shelteredObject.getRestrictedNamespace(this); Then, before calling secretMethod( ), Caller first checks whether key refers to a valid namespace. If it does, then Caller uses key as the namespace when invoking secureMethod( ): if (key != null) { shelteredObject.key::secureMethod( ); } For convenience, our Caller class wraps the code that calls secretMethod( ) in a method named callSecretMethod( ): public function callSecretMethod ( ):void { var key:Namespace = shelteredObject.getRestrictedNamespace(this); if (key != null) { shelteredObject.key:: secretMethod( ); } } Example 17-6 shows the entire code for the Caller class, including callSecretMethod( ) and another convenience method, displaySecret( ), which accesses the restricted vari- able secretData using the same basic technique. return null; } } } Example 17-5. The ShelteredClass class (continued) 346 | Chapter 17: Namespaces Example: Program Modes Our last example is an electronic dictionary that translates from Japanese to English and vice versa. The dictionary demonstrates program modes—perhaps the area of namespace programming in ActionScript with the greatest potential. When in “Japa- nese mode,” the dictionary returns English translations for Japanese queries; when in “English mode,” the dictionary returns Japanese translations for English queries. Each mode is represented by a namespace—japanese for Japanese-to-English mode and english for English-to-Japanese mode. Here are the participants in this example: japanese A namespace for Japanese-specific variables and methods english A namespace for English-specific variables and methods QueryManager class Performs searches for words SearchOptions class Contains the basic options for a search operation Example 17-6. The Caller class package { import flash.display.*; public class Caller extends Sprite { private var shelteredObject:ShelteredClass; public function Caller ( ) { shelteredObject = new ShelteredClass( ); callSecretMethod( ); displaySecret( ); } public function callSecretMethod ( ):void { var key:Namespace = shelteredObject.getRestrictedNamespace(this); if (key != null) { shelteredObject.key::secretMethod( ); } } public function displaySecret ( ):void { var key:Namespace = shelteredObject.getRestrictedNamespace(this); if (key != null) { trace(shelteredObject.key::secretData); } } } } Applied Namespace Examples | 347 JapaneseSearchOptions class Contains options specific to a Japanese search operation EnglishSearchOptions class Contains options specific to an English search operation JEDictionary class The main application class Let’s look at these participants one at a time, bearing in mind that this example is not fully functional, and uses placeholder code where actual database searches would occur. We’ll start with the japanese and english namespace definitions, whose code should be familiar by now: package { public namespace english = "http://www.example.com/jedict/english"; } package { public namespace japanese = "http://www.example.com/jedict/japanese"; } Next comes the QueryManager class, which defines two methods to look up a word, japanese::search( ) and english::search( ). The appropriate search method is invoked depending on the current mode of the program. Each search method accepts an options argument that specifies search options in the form of either a JapaneseSearchOptions or an EnglishSearchOptions object, respectively. Later, in the JEDictionary class, we’ll see that the search options are selected according to the cur- rent program mode. Here’s the code for QueryManager: package { public class QueryManager { japanese function search (word:String, options:JapaneseSearchOptions):Array { trace("Now searching for '" + word + "'.\n" + " Match type: " + options.getMatchType( ) + "\n" + " English language variant: " + options.getEnglishVariant( )); // Code here (not shown) would search the Japanese-to-English // dictionary and return the results, but we'll just return a // hard-coded list of results as a proof-of-concept: return ["English Word 1", "English Word 2", "etc"]; } english function search (word:String, options:EnglishSearchOptions):Array { trace("Now searching for '" + word + "'.\n" + " Match type: " + options.getMatchType( ) + "\n" + " Use kanji in results: " + options.getKanjiInResults( )); // Code here (not shown) would search the English-to-Japanese 348 | Chapter 17: Namespaces // dictionary and return the results, but we'll just return a // hard-coded list of results as a proof-of-concept: return ["Japanese Word 1", "Japanese Word 2", "etc"]; } } } Now let’s examine the three search-options classes: SearchOptions and its two sub- classes, JapaneseSearchOptions and EnglishSearchOptions. The SearchOptions class specifies how the program should look for the requested search string, either using an “exact match” (the matching word must be identical to the search string), a “starts-with match” (all matching words must start with the search string), or a “con- tains match” (all matching words must contain the search string). The different types of matches are represented by the constants MATCH_EXACT, MATCH_ STARTSWITH, and MATCH_CONTAINS. The match type for a given search can be set and retrieved via the methods setMatchType( ) and getMatchType( ). Here’s the SearchOptions class: package { public class SearchOptions { public static const MATCH_EXACT:String = "Exact"; public static const MATCH_STARTSWITH:String = "StartsWith"; public static const MATCH_CONTAINS:String = "Contains"; private var matchType:String; public function SearchOptions ( ) { // Default to exact matching. setMatchType(SearchOptions.MATCH_EXACT); } public function getMatchType ( ):String { return matchType; } public function setMatchType (newMatchType:String):void { matchType = newMatchType; } } } The JapaneseSearchOptions class extends SearchOptions, adding options relevant to Japanese-to-English searches only—namely, whether results should be returned in U.S. English or U.K. English. These two English variants are represented by the con- stants ENGLISH_UK and ENGLISH_US. The English variant for a given search can be set and retrieved via the methods setEnglishVariant( ) and getEnglishVariant( ). package { public class JapaneseSearchOptions extends SearchOptions { public static const ENGLISH_UK:String = "EnglishUK"; public static const ENGLISH_US:String = "EnglishUS"; Applied Namespace Examples | 349 private var englishVariant:String; public function JapaneseSearchOptions ( ) { setEnglishVariant(JapaneseSearchOptions.ENGLISH_UK); } public function getEnglishVariant ( ):String { return englishVariant; } public function setEnglishVariant (newEnglishVariant:String):void { englishVariant = newEnglishVariant; } } } Like JapaneseSearchOptions, the EnglishSearchOptions class extends SearchOptions, adding options relevant to English-to-Japanese searches only—namely, whether results should be returned in kanji (a ideographic character set) or hiragana (a pho- netic character set). The character set for a given search can be set and retrieved via the methods setKanjiInResults( ) and getKanjiInResults( ): package { public class EnglishSearchOptions extends SearchOptions { private var kanjiInResults:Boolean = false; public function getKanjiInResults ( ):Boolean { return kanjiInResults; } public function setKanjiInResults (newKanjiInResults:Boolean):void { kanjiInResults = newKanjiInResults; } } } Finally let’s turn to JEDictionary, the application’s main class, where most of the namespace magic happens. Skim the class code in Example 17-7, then we’ll study it line by line. Example 17-7. The JEDictionary class package { import flash.display.Sprite; public class JEDictionary extends Sprite { private var queryMan:QueryManager; japanese var options:JapaneseSearchOptions; english var options:EnglishSearchOptions; private var lang:Namespace; 350 | Chapter 17: Namespaces To begin, the application’s main class, JEDictionary extends Sprite: public class JEDictionary extends Sprite { To perform searches, JEDictionary creates a QueryManager instance, which it assigns to the variable queryMan: private var queryMan:QueryManager; Next, JEDictionary creates two variables, both with the local name options, but qual- ified by the japanese and english namespaces. These hold the search options that will be passed to the QueryManager class’s instance method search( ). Notice that their datatypes correspond to the type of search being performed: japanese var options:JapaneseSearchOptions; english var options:EnglishSearchOptions; public function JEDictionary( ) { queryMan = new QueryManager( ); japanese::options = new JapaneseSearchOptions( ); japanese::options.setMatchType(SearchOptions.MATCH_STARTSWITH); japanese::options.setEnglishVariant(JapaneseSearchOptions.ENGLISH_US); english::options = new EnglishSearchOptions( ); english::options.setMatchType(SearchOptions.MATCH_CONTAINS); english::options.setKanjiInResults(true); // Do a Japanese search... setModeJapaneseToEnglish( ); findWord("sakana"); // Do an English search... setModeEnglishToJapanese( ); findWord("fish"); } public function findWord (word:String):void { var words:Array = queryMan.lang::search(word, lang::options); trace(" Words found: " + words); } public function setModeEnglishToJapanese ( ):void { lang = english; } public function setModeJapaneseToEnglish ( ):void { lang = japanese; } } } Example 17-7. The JEDictionary class (continued) Applied Namespace Examples | 351 Then comes the definition of the important lang variable, which refers to the namespace corresponding to the current dictionary mode (either Japanese or English): private var lang:Namespace; That’s it for JEDictionary’s variables; now let’s examine its methods: setModeEnglishtoJapanese( ), setModeJapaneseToEnglish( ), and findWord( ). The setModeEnglishtoJapanese( ) and setModeJapaneseToEnglish( ) methods activate the different modes of the dictionary by setting the variable lang to the english namespace or japanese namespace, respectively: public function setModeEnglishToJapanese ( ):void { lang = english; } public function setModeJapaneseToEnglish ( ):void { lang = japanese; } The findWord( ) method uses QueryManager to perform a dictionary lookup using the appropriate search( ) method. The call to search( ) is the most important line of code in our dictionary example: queryMan.lang::search(word, lang::options) Notice that the namespace (the program mode) determines both the type of search to perform (the behavior) and the type of options to be used for that search (the data). When lang is set to japanese, then japanese::search( ) is invoked and passed a JapaneseSearchOptions object. When lang is set to english, then english::search( ) is invoked and passed an EnglishSearchOptions object. The result of the search( ) invocation is assigned to the local variable words and then displayed in a debugging message: public function findWord (word:String):void { var words:Array = queryMan.lang::search(word, lang::options); trace(" Words found: " + words); } For demonstration purposes, the JEDictionary constructor method performs two example dictionary searches (though, in a full-featured application, dictionary searches would normally be performed in response to user input). Searches are car- ried out by the application’s QueryManager instance, which is created in the con- structor, as follows: queryMan = new QueryManager( ); Default options for all Japanese-to-English searches and English-to-Japanese searches are also set in the constructor: japanese::options = new JapaneseSearchOptions( ); japanese::options.setMatchType(SearchOptions.MATCH_STARTSWITH); 352 | Chapter 17: Namespaces japanese::options.setEnglishVariant(JapaneseSearchOptions.ENGLISH_US); english::options = new EnglishSearchOptions( ); english::options.setMatchType(SearchOptions.MATCH_CONTAINS); english::options.setKanjiInResults(true); To perform a search, the constructor sets the dictionary mode, then passes the search string to the JEDictionary class’s instance method findWord( ): // Do a Japanese search... setModeJapaneseToEnglish( ); findWord("sakana"); // Do an English search... setModeEnglishToJapanese( ); findWord("fish"); According to the current dictionary mode, the appropriate search( ) method is called, and the appropriate search options are used. And that completes our dictionary! And it also completes our study of namespaces. Remember you can download the source code for the dictionary application and other examples from this chapter at http://www.moock.org/eas3/examples. Final Core Topics We’re almost finished with our exploration of the core ActionScript language. The coming two chapters cover two final subjects: creating and manipulating XML-based data and Flash Player security restrictions. 353 Chapter 18 CHAPTER 18 XML and E4X19 Since Flash Player 5, ActionScript has included tools for working with XML- structured data. In ActionScript 1.0 and ActionScript 2.0, XML data was created and manipulated with the variables and methods of the built-in XML class (e.g., firstChild, nextSibling, appendChild( ), etc.). The XML class was based on the W3C Document Object Model, or DOM, a standard for interacting with XML docu- ments programmatically (see http://www.w3.org/DOM). As of ActionScript 3.0, the toolset for creating and manipulating XML has been com- pletely overhauled. ActionScript 3.0 implements ECMAScript for XML (“E4X”), an official ECMA-262 language extension for working with XML as a native datatype. E4X seeks to improve the usability and flexibility of working with XML in ECMA- 262-based languages (including ActionScript and JavaScript). Understanding XML Data as a Hierarchy Before we can learn to manipulate XML data with E4X, we must first understand the general principle of XML as hierarchical data. Both the legacy XML class and E4X treat XML data as a hierarchical tree in which each element and text block is consid- ered a tree node (i.e., a branch or a leaf). For example, consider the XML fragment in Example 18-1. (An XML fragment is a section of XML excerpted from an XML document.) Example 18-1. An example XML fragment <BOOK ISBN="0141182806"> <TITLE>Ulysses Joyce, James Penguin Books Ltd 354 | Chapter 18: XML and E4X The elements , , <AUTHOR>, and <PUBLISHER>, and the text “Ulysses”, “Joyce, James”, and “Penguin Books Ltd” are all considered nodes on the tree, as depicted in Figure 18-1. The element <BOOK> is the root of the tree—known as the root node of the XML data structure. Every well-formed XML document must have an all-encompassing root element, such as <BOOK>, that contains every other element. When a node is contained by another node, the contained node is said to be a child of the containing node; conversely, the containing node is known as the child node’s parent. In our example, the <TITLE> element is a child of <BOOK>, and <BOOK> is <TITLE>’s parent. Perhaps surprisingly, <TITLE> is not the first child of <BOOK>; it is the second. The first child is actually the so-called insignificant whitespace (the new line and two spaces) in the XML source code between the <BOOK> and <TITLE> tags. In E4X, insignificant whitespace means any of the following four formatting characters: space (\u0020), carriage return (\u000D), line feed (\u000A), and tab (\u0009). In an XML tree, text blocks—even ones that contain whitespace only—are considered nodes on the tree. Accordingly, the <BOOK> element has not three children but seven, four of which are so-called whitespace nodes (text nodes that contain insignificant whitespace only). The <BOOK> node’s seven children are known as siblings of one another because they reside on the same level in the hierarchy. For example, we say that <TITLE>’s next sib- ling is a whitespace node, and <AUTHOR>’s previous sibling is another whitespace node. You can see how the text nodes get in the way when moving from sibling to sibling in a hierarchy. Fortunately, by default, whitespace nodes are ignored by the E4X parser. E4X lets us treat <AUTHOR> as <TITLE>’s next sibling, which is what we want in Figure 18-1. An example XML hierarchy <BOOK> <TITLE> <AUTHOR> <PUBLISHER> “Penguins Books Ltd” “Joyce, James” “Ulysses” whitespace whitespace whitespace whitespace Representing XML Data in E4X | 355 most cases. You won’t have to process whitespace nodes yourself in E4X unless you specifically want to (see the XML class’s instance variable ignoreWhitespace, dis- cussed in the later section “Converting an XML Element to a String”). On the last tier in the hierarchy, we find that the <TITLE>, <AUTHOR>, and <PUBLISHER> nodes each have a single text-node child: "Ulysses", "Joyce, James", and "Penguin Books Ltd", respectively. The text nodes are the last nodes in the tree. The text contained by an element in XML source code is considered a child node of that element in the corresponding XML tree hierarchy. We’ve now finished examining the XML tree for Example 18-1, but we still haven’t learned where the attributes fit into the hierarchy. You might expect <BOOK>’s ISBN attribute to be depicted as a child node called ISBN. But in practice, an attribute is not considered a child of the element that defines it, but rather a characteristic of that ele- ment. We’ll learn how attributes are accessed in E4X in the later section “Accessing Attributes.” Now that we’ve learned how XML data can be thought of as a conceptual hierarchy, we can explore how XML is represented, created, and manipulated using E4X techniques. Representing XML Data in E4X In E4X, XML data is represented by one of two native ActionScript datatypes, XML and XMLList and their corresponding classes, also named XML and XMLList. Due to the introduction of the E4X XML datatype, the legacy XML class from ActionScript 1.0 and ActionScript 2.0 has been renamed to XMLDocument in ActionScript 3.0 and moved to the flash.xml package. Each XML instance represents one of the following five possible kinds of XML con- tent, known as node kinds: • An element • An attribute • A text node • A comment • A processing instruction If an XML element has any child elements (e.g., <BOOK>’s child <AUTHOR>) or child text nodes (e.g., <TITLE>’s child “Ulysses"), those children are wrapped in an XMLList by 356 | Chapter 18: XML and E4X their parent XML instance. Each XMLList instance is an arbitrary collection of one or more XML instances. For example, an XMLList might be any of the following: • A series of attributes or elements returned by a search • A group of XML fragments, each with its own root element • A collection of the text nodes in a document • A collection of the comments in a document • A collection of the processing instructions in a document The child nodes of an XML element are always wrapped in an XMLList. Even if an element has only one child (say, just a text node), that child is still wrapped in an XMLList. If an XML element has any attributes, comments, or processing instructions, those are likewise wrapped in an XMLList by the parent XML instance. However, comments and processing instructions are, by default, ignored by the E4X parser. (To prevent them from being ignored, set the static variables XML.ignoreComments and XML.ignoreProcessingInstructions to false.) Let’s look at an example showing how an XML fragment would be represented by instances of XML and XMLList classes in E4X. Recall the XML source code from Example 18-1: <BOOK ISBN="0141182806"> <TITLE>Ulysses Joyce, James Penguin Books Ltd From the E4X perspective, the element in the preceding code is represented by an XML instance. That XML instance contains two XMLList instances—one for ’s attributes and the other for its child elements. The element has only one attribute, so the XMLList for ’s attributes contains one XML instance only (representing the attribute ISBN). The XMLList for ’s child elements contains three XML instances, representing the three elements , <AUTHOR>, and <PUBLISHER>. Each of those XML instances, itself, has an XMLList containing exactly one XML instance representing, respectively, the child text nodes "Ulysses", "Joyce, James", and "Penguin Books Ltd". Figure 18-2 summarizes. In the figure, each item in the <BOOK> hierarchy is labeled with a letter (A through M) so it can be referenced easily in the coming sections. Now let’s put some of the preceding theory into practice by creating the <BOOK> frag- ment from Example 18-1 using E4X techniques. Creating XML Data with E4X | 357 Creating XML Data with E4X To create the <BOOK> XML fragment from Example 18-1 via E4X, we have three gen- eral options: • Use the XML constructor to create a new XML instance, and then create the remainder of the fragment programmatically using the techniques covered in later section “Changing or Creating New XML Content.” • Use the XML constructor to create a new XML instance, and then import the fragment from an externally loaded file, as discussed in the later section “Load- ing XML Data.” • Write our XML data in literal form, just like a string or a number, anywhere lit- erals are allowed by ActionScript. For now, we’ll use the third approach—creating the XML fragment with an XML lit- eral. Example 18-2 demonstrates; it assigns a literal XML value (the XML fragment from Example 18-1) to the variable novel. Figure 18-2. The <BOOK> fragment, represented in E4X Example 18-2. Assigning an XML literal to a variable var novel:XML = <BOOK ISBN="0141182806"> <TITLE>Ulysses Joyce, James Penguin Books Ltd ; XML (ISBN=“0141182806”) XML () XML (<AUTHOR>) XML (<PUBLISHER>) XML List (Child nodes) XML List (Child nodes) XML List (Child nodes) XML (Ulysses) XML (Joyxe, James) XML (Penguin Books Ltd) XML List (Attributes) XML List (Child nodes) XML (<BOOK>) A B CFI DGJ EHK L M XML instance (element) XML instance (attribute) XMLList instance XML instance (text node) 358 | Chapter 18: XML and E4X When the preceding code runs, ActionScript generates a new E4X XML instance rep- resenting the literal XML fragment and assigns it to the variable novel. To view the XML source code for an XML instance (such as the one referenced by novel), use the XML class’s instance method toXMLString( ), as in: trace(novel.toXMLString( )); The toXMLString( ) method is covered later in the section “Convert- ing XML and XMLList to a String.” Notice that the line breaks and quotation marks in the preceding XML literal are per- fectly normal. ActionScript knows they are part of the XML data, and interprets them as such. Where possible, ActionScript even converts certain reserved charac- ters to XML entities. For details see the section “Using XML Entities for Special Characters.” ActionScript also allows dynamic expressions to be used within an XML literal so that element names, attribute names, attribute values, and element content can be generated programmatically. To specify a dynamic expression within an XML literal, surround it in curly braces ({}). For example, the following code specifies the name of the <BOOK> tag dynamically: var elementName:String = "BOOK"; var novel:XML = <{elementName}/>; The following code presents a slightly exaggerated example that creates the same XML hierarchy as that shown in Example 18-2, but dynamically specifies all element names, attribute names, attribute values, and element contents. var rootElementName:String = "BOOK"; var rootAttributeName:String = "ISBN"; var childElementNames:Array = ["TITLE", "AUTHOR", "PUBLISHER"]; var bookISBN:String = "0141182806"; var bookTitle:String = "Ulysses"; var bookAuthor:String = "Joyce, James"; var bookPublisher:String = "Penguin Books Ltd"; var novel:XML = <{rootElementName} {rootAttributeName}={bookISBN}> <{childElementNames[0]}>{bookTitle}</{childElementNames[0]}> <{childElementNames[1]}>{bookAuthor}</{childElementNames[1]}> <{childElementNames[2]}>{bookPublisher}</{childElementNames[2]}> </{rootElementName}>; Note that because the characters {}are used to delimit a dynamic expression, they are not allowed in some parts of an XML literal. Specifically, within an element name, an attribute name, or element content, the entities { and } must be used to represent { and }, respectively. However, { and } can be used in literal form within an attribute value, a CDATA section, a processing instruction, or a comment. Accessing XML Data | 359 Now that we have a variable, novel, defined in Example 18-2 that references the XML fragment from Example 18-1, let’s see how its various parts can be accessed using E4X coding techniques. Accessing XML Data E4X offers two general sets of tools for accessing data in an XML hierarchy: • The XML and XMLList content-access methods (attribute( ), attributes( ), child( ), children( ), comments( ), descendants( ), elements( ), parent( ), processingInstructions(), and text( )) • Variable-style access with the dot (.), descendant (..), and attribute (@) operators Variable-style access is offered as a convenience to the programmer and always equates to one of the methods of either the XML or XMLList classes. However, the two approaches do not overlap completely; the following types of content must be accessed using the appropriate method of the XML or XMLList class: •AnXML instance’s parent (accessed via parent( )) • Comments (accessed via comments( )) • Processing instructions (accessed via processingInstructions( )) • Elements or attributes whose names include characters considered illegal in an ActionScript identifier (accessed via attribute( ), child( ), descendants( ),or elements( )) Continuing with our <BOOK> example, let’s take a look at some of the most common ways to access XML data. Accessing the Root XML Node In Example 18-2 we assigned the XML fragment from Example 18-1 to the variable novel. To access the root <BOOK> element of that fragment (item A in Figure 18-2) we refer to it as, simply, novel. For example, the following code passes the <BOOK> ele- ment (and, by extension, all its children) to the hypothetical addToOrder( ) method: addToOrder(novel); Notice that the <BOOK> element is not named. That is, we write addToOrder(novel), not either of the following: addToOrder(novel.BOOK); // Wrong. addToOrder(novel.child("BOOK")); // Also wrong. The preceding two examples mistakenly treat the <BOOK> element as though it were a child of novel, which is not. We’ll learn how to access child elements in the next section. 360 | Chapter 18: XML and E4X Note that there is no direct way to access the root node relative to any given child. However, we can use the XML class’s instance method parent( ) (covered later) to ascend a tree recursively to its root, as shown in Example 18-3. Accessing Child Nodes To access the XMLList representing <BOOK>’s child nodes (item B in Figure 18-2), we use the XML class’s instance method children( ), which takes no arguments. For example: novel.children( ) // Returns an XMLList representing <BOOK>'s child nodes Alternatively, we can access <BOOK>’s child nodes using E4X’s more convenient properties wildcard (*). For example: novel.* // Also returns an XMLList, representing <BOOK>'s child nodes To access a specific child in an XMLList we use the familiar array-element access operator, []. For example, to access the <BOOK> element’s second child, <AUTHOR> (item D in Figure 18-2), we use: novel.children( )[1] // A reference to <BOOK>'s second child node or: novel.*[1] // Also a reference to <BOOK>'s second child node Although there is no firstChild or lastChild variable in E4X (as there is in the leg- acy XMLDocument class), the first child in a list of child nodes can be accessed as follows: theNode.children( )[0] And the last child in a list of child nodes can be accessed as follows: theNode.children( )[theNode.children().length( )-1] However, accessing a child node according to its position in a list can be cumber- some, and has, therefore, been deemphasized by E4X. In E4X, child nodes are typi- cally accessed by their element names rather than their position. To access child Example 18-3. A custom root-access method // Returns the root of an XML hierarchy, relative to a given child public function getRoot (childNode:XML):XML { var parentNode:XML = childNode.parent( ); if (parentNode != null) { return getRoot(parentNode); } else { return childNode; } } // Usage: getRoot(someChild); Accessing XML Data | 361 nodes by name, we use the XML class’s instance method child( ), which returns an XMLList of all child elements matching a specified name. For example, to retrieve an XMLList of all children of <BOOK> named "AUTHOR", we use: novel.child("AUTHOR") // Returns all child elements of <BOOK> named "AUTHOR" Alternatively, we can access child nodes by name using E4X’s more convenient vari- able-access syntax. The following code has the identical result as the preceding code but uses E4X’s more convenient variable-access syntax: novel.AUTHOR // Also returns all child elements of <BOOK> named "AUTHOR" If <BOOK> contained two <AUTHOR> elements, then novel.AUTHOR would return an XMLList with two XML instances, representing those elements. To access the first element, we would use novel.AUTHOR[0]. To access the second element, we would use novel.AUTHOR[1], as shown in the following code: var novel:XML = <BOOK> <AUTHOR>Jacobs, Tom</AUTHOR> <AUTHOR>Schumacher, Jonathan</AUTHOR> </BOOK>; novel.AUTHOR[0]; // Access <AUTHOR>Jacobs, Tom</AUTHOR> novel.AUTHOR[1]; // Access <AUTHOR>Schumacher, Jonathan</AUTHOR> Of course, the <BOOK> element from Example 18-1 contains only one child named "AUTHOR", so the XMLList returned by the expression novel.AUTHOR has just one XML instance (representing the lone <AUTHOR> element). To access that <AUTHOR> element, we could use this code: novel.AUTHOR[0] // A reference to the <AUTHOR> instance However (and this is the exciting part!), in most cases we don’t have to include the [0]. In order to make node access more convenient, E4X implements special behav- ior for XMLList objects that have only one XML instance (as our example novel. AUTHOR does). When an XML method is invoked on an XMLList with only one XML instance, the method invocation is automatically forwarded to that XML instance. By forwarding the method invocation, E4X lets the programmer treat an XMLList with only one XML instance as though it were that instance. As the E4X specification puts it, E4X “intentionally blurs the distinction between an individual XML object and an XMLList containing only that object.” For example, suppose we want to change the <AUTHOR> element’s name from "AUTHOR" to "WRITER". We could use this code, which explicitly refers to the <AUTHOR> instance: novel.AUTHOR[0].setName("WRITER"); But we would typically use this more convenient code, which implicitly refers to the <AUTHOR> instance by omitting the array-element access (the [0] following novel.AUTHOR): novel.AUTHOR.setName("WRITER"); 362 | Chapter 18: XML and E4X When we invoke setName( ) directly on the XMLList returned by novel.AUTHOR, ActionScript recognizes that the list has only one XML instance (<AUTHOR>) and auto- matically forwards the setName( ) invocation to that instance. As a result, the name of the sole element contained by novel.AUTHOR is changed from "AUTHOR" to "WRITER". In most cases, this sleight-of-hand performed by ActionScript makes XML code eas- ier to write and more intuitive to read. However, some caution is required when using this technique. For example, the following code invokes setName( ) on an XMLList with more than one XML instance: var novel:XML = <BOOK> <AUTHOR>Jacobs, Tom</AUTHOR> <AUTHOR>Schumacher, Jonathan</AUTHOR> </BOOK>; novel.AUTHOR.setName('WRITER'); When the preceding code runs, ActionScript generates the following runtime error: The setName method works only on lists containing one item. The act of treating an XMLList with only one XML instance as though it were that instance is an important and often misunderstood aspect of E4X programming, so we’ll return to this topic several times over the course of this chapter. Accessing Text Nodes As we learned in the earlier section “Understanding XML Data as a Hierarchy,” the text contained by an element is represented as a node in an XML hierarchy. For example, in the following XML fragment (repeated from Example 18-2) the text "Ulysses" is a text node. It is represented by an XML instance whose node kind is “text,” as are the text nodes "Joyce, James", and "Penguin Books Ltd". var novel:XML = <BOOK ISBN="0141182806"> <TITLE>Ulysses Joyce, James Penguin Books Ltd ; We access text nodes in different ways depending on our needs. When we need to reference a text node as an XML instance, we must use the child-node access syntax discussed in the previous section. For example, to access the text "Ulysses", which is ’s first child, we can use this code: novel.TITLE.children( )[0] // A reference to the text node Ulysses Or, alternatively, we can use the properties wildcard to do the same thing: novel.TITLE.*[0] // Also a reference to the text node Ulysses Accessing XML Data | 363 Both of the preceding examples return an XML object (not a string) that represents the element text "Ulysses". We can invoke XML methods on that object, just as we can with any XML object. For example: novel.TITLE.*[0].parent( ) // Reference to the <TITLE> element novel.TITLE.*[0].nodeKind( ) // Returns the string "text" novel.TITLE.*[0].toString( ) // Returns the string "Ulysses" However, if we simply want to access the content of a text node as a String, not an XML instance, we can use the XML class’s instance method toString( ) on its parent ele- ment. For elements such as <TITLE> that contain one child text node only (with no other interspersed elements), toString( ) returns the text of that child node, omitting the parent element’s start and end tags. Hence, the expression novel.TITLE.toString( ) yields the string "Ulysses": trace(novel.TITLE.toString( )); // Displays: Ulysses As you’re mulling over the preceding line of code, remember that it is actually a shorthand version of: trace(novel.TITLE[0].toString( )); // Displays: Ulysses The shorthand expression novel.TITLE.toString( ) returns "Ulysses" because ActionScript recognizes that the XMLList referred to by novel.TITLE has only one XML instance (<TITLE>) and automatically forwards the toString( ) invocation to that instance. When accessing the content of a text node as a String, we can typically omit the explicit call to toString( ) because ActionScript invokes toString( ) automatically whenever a nonstring value is used where a string is expected. For example, the trace( ) function expects a string as an argument, so instead of explicitly invoking toString( ), as in: trace(novel.TITLE.toString( )); // Displays: Ulysses we can let ActionScript invoke it implicitly: trace(novel.TITLE); // Also displays: Ulysses Likewise, when assigning the content of the text node Ulysses to a variable of type String, instead of this fully explicit code: var titleName:String = novel.TITLE[0].toString( ); we can use, simply: var titleName:String = novel.TITLE; Now that’s snazzy. And it’s also the typical way to retrieve the text contained by an element in E4X. For text nodes that are interspersed with other elements, we can use the XML class’s instance method text( ) to retrieve the text nodes not contained by elements. To 364 | Chapter 18: XML and E4X illustrate how this works, let’s temporarily add a <DESCRIPTION> element to <BOOK>,as follows: var novel:XML = <BOOK ISBN="0141182806"> <TITLE>Ulysses Joyce, James Penguin Books Ltd A very thick book. ; The element contains both element and text child nodes: • A (text node) • very (element node with a child text node) • thick book. (text node) To retrieve an XMLList with the two text nodes A and thick book., we use: novel.DESCRIPTION.text( ) To access those text nodes, we use the array-element access operator: trace(novel.DESCRIPTION.text( )[0]); // Displays: A trace(novel.DESCRIPTION.text( )[1]); // Displays: thick book. The text( ) method can also be used to retrieve the text nodes from an entire XMLList, not just a single XML element. For example, suppose we have an XMLList representing the children of the element from Example 18-2 (as it existed before we added the element): novel.* To place the text nodes from each of those children into an XMLList for easy pro- cessing, such as for the creation of a user interface, we use: novel.*.text( ) Once again, to access the text nodes, we use the array-element access operator: trace(novel.*.text( )[0]); // Displays: Ulysses trace(novel.*.text( )[1]); // Displays: Joyce, James trace(novel.*.text( )[2]); // Displays: Penguin Books Ltd However, the XMLList class’s instance method text( ) is less useful when applied to a list of elements that contain both text and element child nodes. For any node that contains both text and element child nodes (such as the node), only the first child text node is returned; other children are ignored. For example: var novel:XML = Ulysses Joyce, James Penguin Books Ltd A very thick book. ; trace(novel.*.text( )[3]); // Displays: A Accessing XML Data | 365 // The other child nodes, very and // thick book., are ignored. Accessing Parent Nodes To access a node’s parent node, we use the XML class’s instance method parent( ), which takes no arguments. For example, suppose a variable, pub, has a reference to the element from Example 18-2. var pub:XML = novel.PUBLISHER[0]; To access ’s parent (which is ), we use: pub.parent( ) The parent( ) method can also be used successively to access any ancestor node, as shown in the following code: // Create a 3-tier XML hierarchy. var doc:XML = ; // Assign a reference to var kid:XML = doc.parent.child[0]; // Use parent( ) successively to access from var grandparent:XML = kid.parent().parent( ); Unlike children( ) and child( ), the XML class’s instance method parent( ) method has no alternative variable-access syntax. When used on an XMLList instance, the parent( ) method returns null unless all items in the list have the same parent, in which case that parent is returned. For example, in the following code, we retrieve an XMLList representing the ele- ment’s three children, and then invoke parent( ) on that list. Because the three chil- dren have the same parent, that parent is returned. var bookDetails:XMLList = novel.*; var book:XML = bookDetails.parent( ); // Returns the element Invoking parent( ) on an XMLList with a single XML instance is identical to invoking parent( ) on that instance itself. For example, the following two lines of code are identical: novel.PUBLISHER[0].parent( ) // Accesses novel.PUBLISHER.parent( ) // Also accesses When parent( ) is invoked on an XML instance that represents an attribute, it returns the element on which the attribute is defined. The following code demonstrates, using an attribute-access technique that we haven’t yet covered (but will very shortly): novel.@ISBN.parent( ) // Returns the element 366 | Chapter 18: XML and E4X Accessing Sibling Nodes As we learned in the section “Understanding XML Data as a Hierarchy,” a sibling node is a node that resides directly beside another node on a given level of an XML hierarchy. For example, in our familiar hierarchy, is the previous sib- ling of <AUTHOR> and <PUBLISHER> is the next sibling of <AUTHOR>. var novel:XML = <BOOK ISBN="0141182806"> <TITLE>Ulysses Joyce, James Penguin Books Ltd ; In E4X there is no built-in support for moving between sibling nodes in an XML hierarchy. The DOM-based nextSibling, previousSibling variables are not part of the E4X API. However, the next sibling of any given node can be deduced using the following code, provided that the node has a valid parent node: someNode.parent( ).*[someNode.childIndex( )+1]; And the previous sibling can be found using the following code: someNode.parent( ).*[someNode.childIndex( )-1]; For example, the following code accesses ’s previous and next siblings: var author:XML = novel.AUTHOR[0]; // Previous sibling trace(author.parent().*[author.childIndex( )-1]); // Displays: Ulysses // Next sibling trace(author.parent().*[author.childIndex( )+1]); // Displays: // Penguin Books Ltd Example 18-4 wraps the code for accessing a node’s previous sibling in a custom method. Notice that the method adds code to check that the specified node actually has a previous sibling before returning it. Example 18-4. A custom previousSibling( ) method public function previousSibling (theNode:XML):XML { // Make sure the node actually has a previous sibling before // attempting to return it if (theNode.parent() != null && theNode.childIndex( ) > 0) { return theNode.parent().*[theNode.childIndex( )-1]; } else { return null; } } // Usage: previousSibling(someNode); Accessing XML Data | 367 Example 18-5 defines nextSibling( ), the companion custom method to the previousSibling( ) method defined in Example 18-4. Notice that the method adds code to check that the specified node actually has a next sibling before returning it. E4X reduces the emphasis on accessing siblings due to its increased focus on accessing elements by name. For example, to access the element in E4X, we would typically use, simply, novel.TITLE, not author.parent().*[author.childIndex( )-1]). Accessing Attributes To access an XMLList representing all of an element’s attributes, we use the XML class’s instance method attributes( ), which takes no arguments, and has the general form: someElement.attributes( ) For example, the following code returns an XMLList representing <BOOK>’s attributes (item L in Figure 18-2): novel.attributes( ) Alternatively, we can access an XMLList representing an element’s attributes using the more convenient E4X attributes wildcard (@*), which is written as: someElement.@* // Returns an XMLList representing // all of someElement's attributes For example, the following code, which is equivalent to novel.attributes( ), returns an XMLList representing <BOOK>’s attributes (again, item L in Figure 18-2): novel.@* As with elements, attributes in an XMLList can be accessed using the array-access operator ([]). For example, the following code accesses the first, and only, attribute of the <BOOK> element, ISBN (item M in Figure 18-2): novel.attributes( )[0] Example 18-5. A custom nextSibling( ) method public function nextSibling (theNode:XML):XML { if (theNode.parent( ) != null && theNode.childIndex() < theNode.parent().children().length( )-1) { return theNode.parent().*[theNode.childIndex( )+1]; } else { return null; } } // Usage: nextSibling(someNode); 368 | Chapter 18: XML and E4X The following code also accesses <BOOK>’s first attribute (again, ISBN), but uses E4X’s attributes wildcard syntax: novel.@*[0] However, neither novel.@*[0] nor novel.attributes( )[0] represents typical E4X code. In E4X, it’s rare to access attributes according to their order in an XML docu- ment. Normally, attributes are accessed by name, using either the attribute( ) method or E4X’s more convenient variable-access syntax. The general form for accessing an attribute by name using the attribute( ) method is: someElement.attribute("attributeName") The preceding code returns an XMLList containing the attribute named attributeName of the element someElement. For example, the following code returns an XMLList that contains one XML instance, representing <BOOK>’s ISBN attribute (item M in Figure 18-2): novel.attribute("ISBN") Here’s the equivalent form for accessing an attribute by name using variable-access syntax is: someElement.@attributeName For example, the following also returns an XMLList that contains one XML instance, representing <BOOK>’s ISBN attribute, but uses variable-access syntax: novel.@ISBN Like child( ), attribute( ) returns an XMLList of XML instances matching a given name. However, because it is an error for two or more attributes of the same ele- ment to have the same name, the XMLList returned by attribute( ) always contains one XML instance only (representing the attribute by the specified name). To access the XML instance contained by the XMLList returned by novel.@ISBN,we could use: novel.@ISBN[0] But, when invoking an XML method on that instance, we normally omit the array- access operation ([0]), as in: novel.@ISBN.someXMLMethod() We can omit [0] because, as we learned earlier, when an XML method is invoked on an XMLList with only one XML instance, the method invocation is automatically forwarded to that XML instance. For example, the following explicit code: novel.@ISBN[0].parent( ) // Returns the <BOOK> node is equivalent to the following implicit code: novel.@ISBN.parent( ) // Also returns the <BOOK> node Accessing XML Data | 369 That said, XML instances representing attributes never have children, and, hence, have no need for the majority of the XML class’s methods. Instead, an XML instance representing an attribute is used nearly exclusively for the simple attribute value it represents. To access the value of an attribute, we use the XML class’s instance method toString( ). For example, the following code assigns the value of <BOOK>’s ISBN attribute to the variable bookISBN using fully explicit code: var bookISBN:String = novel.@ISBN[0].toString( ); But remember, we can invoke toString( ) directly on novel.@ISBN (rather than on novel.@ISBN[0]) because it is an XMLList with only one XML instance. Here is the shorter, more typical code: var bookISBN:String = novel.@ISBN.toString( ); // Removed [0] But we can make the preceding line of code shorter still. The XML class is dynamic. Hence, we can use ActionScript’s automatic datatype conversion to convert the value of any XML instance’s variables to a string. (ActionScript’s datatype conversion rules are described in Chapter 8.) Here’s the technique: var bookISBN:String = novel.@ISBN; In the preceding code, novel is an instance of a dynamic class (XML). Hence, when we assign its ISBN variable to the typed variable bookISBN, ActionScript defers type checking until runtime. At runtime, because bookISBN’s datatype is a primitive type (String), ISBN’s value is automatically converted to that primitive type. Pretty handy. And it works for converting to other primitive datatypes, too. For example, the following code converts the ISBN attribute value to a number simply by assigning it to a variable whose datatype is Number: var bookISBN:Number = novel.@ISBN; When working with attributes, remember that an attribute’s value is always type String, even if it contains what appears to be another type of data. To be used as a datatype other than String, that value must be converted either explicitly or implic- itly. To avoid unwelcome surprises, stay mindful of the rules for datatype conver- sion, covered in Chapter 8. In particular, remember that the string value "false" converts to the Boolean value true! When working with attributes that contain Boolean information, it’s, therefore, easier to use string comparisons than it is to convert to the Boolean datatype. For example, the following code adds a new attribute, INSTOCK, to the <BOOK> element, indicating whether or not the book is cur- rently in stock. To print a message indicating the availability of the book, we com- pare novel.@INSTOCK to the string "false" rather than convert novel.@INSTOCK to a Boolean value. As a precaution, we also convert the attribute value to all lowercase before making the comparison. 370 | Chapter 18: XML and E4X When comparing attributes, remember that attributes are always strings and that comparisons are case-sensitive. var novel:XML = <BOOK ISBN="0141182806" INSTOCK="false"> <TITLE>Ulysses Joyce, James Penguin Books Ltd ; // Compare to the string "false" instead of converting to Boolean if (novel.@INSTOCK.toLowerCase( ) == "false") { trace("Not Available!"); } else { trace("Available!"); } Accessing Comments and Processing Instructions The final two kinds of nodes we can access in E4X are comments and processing instructions. XML comments take the for: and XML processing instructions take the form: These two ancillary forms of data can be accessed using the XML class’s instance methods comments( ) and processingInstructions( ). Both methods return an XMLList representing all direct children of an element that are either comments or processing instructions, respectively. However, by default, the E4X parser ignores both com- ments and processing instructions. In order to make the comments of an XML docu- ment or fragment accessible, we must set XML.ignoreComments to false before parsing the data, as in: XML.ignoreComments = false; Similarly, in order to make the processing instructions of an XML document or frag- ment accessible, we must set XML.ignoreProcessingInstructions to false before pars- ing the data, as in: XML.ignoreProcessingInstructions = false; Note that both XML.ignoreComments and XML.ignoreProcessingInstructions are static variables, set through the XML class, not an individual XML instance. Once set, XML.ignoreComments and XML.ignoreProcessingInstructions affect all future XML parsing operations. Example 18-6 adds two comments and two processing instructions to the exam- ple, and demonstrates how to access them. Notice that XML.ignoreProcessingInstructions Accessing XML Data | 371 and XML.ignoreComments are set to false before the XML literal is assigned to the variable novel. Notice also that even though the comments and processing instructions are inter- spersed within ’s children, comments() and processingInstructions() ignore the other children, and return a clean list of the comments and processing instructions. To obtain an XMLList representing all comments and processing instructions within an entire XML tree (not just within the direct children of a node), use the descen- dants operator in combination with the properties wildcard, as follows: var tempRoot:XML = ; tempRoot.appendChild(novel); trace(tempRoot..*.comments( )[0]); // First comment in the document We’ll study the preceding technique more closely in the later section “Traversing XML Trees.” Accessing Attributes and Elements Whose Names Contain Reserved Characters When an attribute or element name contains a character that is considered illegal in an ActionScript identifier (e.g., a hyphen), that attribute or element cannot be accessed using the dot operator. Instead, we must use the attribute( ) method, the child( ) method, or the [] operator. For example: var saleEndsDate:XML = February 1, 2006 trace(saleEndsDate.@TIME-ZONE); // ILLEGAL! Don't do this. trace(saleEndsDate.attribute("TIME-ZONE")); // Legal. Do do this. trace(saleEndsDate.@["TIME-ZONE"]); // Also Legal. Example 18-6. Accessing comments and processing instructions XML.ignoreComments = false; XML.ignoreProcessingInstructions = false; // Create an XML fragment that contains both // comments and processing instructions var novel:XML = Ulysses Joyce, James Penguin Books Ltd trace(novel.comments( )[0]); // trace(novel.comments( )[1]); // trace(novel.processingInstructions( )[0]); // trace(novel.processingInstructions( )[1]); // 372 | Chapter 18: XML and E4X In the specific case of the illegal code saleEndsDate.@TIME-ZONE, ActionScript treats the hyphen as a subtraction operation, and interprets the expression to mean saleEndsDate.@TIME minus ZONE! In all likelihood, no variable (or method) named ZONE exists, and ActionScript will generate the following error message: Access of undefined property 'ZONE' However, if a variable named ZONE did exist, its value would be subtracted from the empty XMLList object represented by saleEndsDate.@TIME, and no error would occur! Without any error message, the failed reference to saleEndsDate.@TIME-ZONE would be very difficult to track down. Given that the attribute saleEndsDate.@TIME does not exist, we would ideally like ActionScript to generate a “nonexistent attribute” error, but unfortunately the version of the E4X specification implemented by ActionScript 3.0 stipulates that references to nonexistent attributes should return an empty XMLList object rather than causing an error. Future versions of Action- Script may improve this situation. We’ve now covered the basics of accessing XML data. Before we continue our study of E4X, let’s return one more time to the important topic of treating an XMLList instance as though it were an XML instance. Treating XMLList as XML, Revisited Earlier we learned that in E4X, a reference to an XMLList with only one XML instance can be treated as though it were that instance. For example, we saw that the expression: novel.AUTHOR[0].setName("WRITER"); was equivalent to the expression: novel.AUTHOR.setName("WRITER"); // Removed [0] The two are equivalent because novel.AUTHOR refers to an XMLList with a single XML instance (the element ). Treating an XMLList instance as though it were an XML instance enables much of E4X’s convenience and usability, but also introduces some potentially confusing sub- tleties, particularly when used in combination with automatic string conversion. Let’s take a deeper look at this issue. Suppose we’re building a user interface for an online book store in which each book is represented by an XML fragment matching the structure of our ongoing example. When the user chooses a book from the store, the corresponding author’s name appears onscreen. In our code, we create a method, displayAuthor( ), that handles the display of the author’s name. In our first attempt to code the displayAuthor( ) method, we require that the name of the author be supplied as a string: Accessing XML Data | 373 public function displayAuthor (name:String):void { // authorField refers to a TextField instance in which to // display the author name authorField.text = name; } When the user chooses a book, we retrieve the name of the author for that book from the element and pass it to the displayAuthor( ) method like this: displayAuthor(novel.AUTHOR); That statement is pleasingly simple and intuitive, but as we’ve learned in this chap- ter, there’s a lot going on behind the scenes. As a review, let’s dissect how it works. First, ActionScript passes novel.AUTHOR to the displayAuthor( ) method as the value of the name parameter. The name parameter’s datatype is String, so ActionScript auto- matically attempts to convert novel.AUTHOR to a string using: novel.AUTHOR.toString( ) By default, calling toString( ) on an object yields a string in the format [object ClassName], but novel.AUTHOR is an XMLList instance, and XMLList overrides toString( ) with custom behavior. Specifically, the XMLList version of toString( ) rec- ognizes that novel.AUTHOR contains only one item, and, therefore, returns the result of calling XML’s toString( ) on that item. So the invocation, novel.AUTHOR.toString( ),is automatically redirected to novel.AUTHOR[0].toString( ). And what is the return value of novel.AUTHOR[0].toString( )? As we learned earlier, the answer hinges on the fact that novel.AUTHOR[0] represents a simple XML element that does not con- tain any child elements. For an XML element that contains no other elements, XML’s toString( ) returns the child text node of that element, as a string, with the containing tags removed. So novel.AUTHOR[0].toString( ) returns "Joyce, James" (not "Joyce, James") as the final value passed to displayAuthor( ). In summary: • Passing novel.AUTHOR to a parameter of type String forces an implicit conversion of novel.AUTHOR to a string. • novel.AUTHOR is converted to a string via novel.AUTHOR.toString( ). • novel.AUTHOR.toString( ) automatically returns novel.AUTHOR[0].toString( ) because novel.AUTHOR is an XMLList with only one item. • novel.AUTHOR[0].toString( ) returns the text contained by the element ("Joyce, James") as per the implementation of XML’s toString( ) (see the later section “Converting XML and XMLList to a String”). After all is said and done, the expression: displayAuthor(novel.AUTHOR); results in: displayAuthor("Joyce, James"); which is what we intuitively expected in the first place. 374 | Chapter 18: XML and E4X Most of the time, we can ignore the preceding complexity because E4X, in unscien- tific terms, “does what it looks like it will do.” But there are times where we must understand the E4X autopilot in order to take manual control. For example, sup- pose we decide that our bookstore should display not just the name, but also the birth date of each author. We modify our XML structure to include the birth date as a child of the element, as shown in the following code: var novel:XML = Ulysses Joyce, James February 2 1882 Penguin Books Ltd Accordingly, we modify the displayAuthor( ) method so that it accepts the entire element as a parameter, and retrieves the author name and birth date from the and child elements directly: public function displayAuthor (author:XML):void { authorField.text = "Name: " + author.NAME + " Birthdate: " + author.BIRTHDATE; } In the preceding code, notice that the parameter datatype has changed from String to XML. If we now attempt to pass novel.AUTHOR to the displayAuthor( ) method, we receive a type mismatch error at runtime because ActionScript cannot implicitly convert novel.AUTHOR (which is an XMLList) to an instance of the XML class: displayAuthor(novel.AUTHOR); // TypeError: Error #1034: Type Coercion // failed: cannot convert XMLList to XML To fixthe error, we must refer to the XML instance representing explicitly when passing it to displayAuthor( ), as in: displayAuthor(novel.AUTHOR[0]); // Pass the lone XML instance // in novel.AUTHOR to displayAuthor( ) Notice the important difference: when we want to access the text contained by the element as a String, we can rely on E4X’s automatic behavior; but when we want to access the actual XML instance representing the element, we must refer to that instance explicitly. Now suppose later, we’re asked to modify our store to handle books with multiple authors. Once again we alter our XML structure, this time to accommodate multiple elements. Example 18-7 contains a sample XML fragment showing the new structure (the authors’ birth dates are fabricated). Example 18-7. A multiple-author fragment var oopBook:XML = Head First Design Patterns Accessing XML Data | 375 To handle the new XML structure, we modify displayAuthor( ) so that it accepts an XMLList representing multiple elements (instead of the previous single element). The new version of displayAuthor( ) uses the for-each-in state- ment to iterate over the elements (we’ll study for-each-in in the later sec- tion “Processing XML with for-each-in and for-in”). public function displayAuthor (authors:XMLList):void { for each (var author:XML in authors) { authorField.text += "Name: " + author.NAME + ", Birthdate: " + author.BIRTHDATE + "\n"; } } To pass a list of the elements to displayAuthor( ), we use the following code: displayAuthor(oopBook.AUTHOR); The preceding line of code matches our original approach, which was: displayAuthor(novel.AUTHOR); But this time, the XMLList is passed directly to the displayAuthor( ) method without any conversion because the receiving parameter’s datatype is XMLList not String. Again, notice the difference: when passing an XMLList object to a function, if we want to convert the list to a String, we specify String as the datatype of the receiving parameter and let E4X’s automatic behavior work its magic; but if we want to pre- serve the datatype of the list, we must specify XMLList as the datatype of the receiv- ing parameter. Both the reference itself (oopBook.author) and the datatype of the receiving parameter (authors) affect the behavior of the code. Table 18-1 reviews the results of passing the various E4X expressions we’ve just stud- ied to parameters of various datatypes. Eric Freeman January 1 1970 Elisabeth Freeman January 1 1971 Kathy Sierra January 1 1972 Bert Bates January 1 1973 O'Reilly Media, Inc ; Example 18-7. A multiple-author fragment (continued) 376 | Chapter 18: XML and E4X Don’t panic. E4X is well thought out. Don’t let its automatic behavior distress you. Most of the time it will serve you well. However, when accessing XML nodes using variable-access syntax(the dot operator), bear the following potential points of con- fusion in mind: • parentNode.childNodeName is equivalent to parentNode.child(childNodeName) and always refers to an XMLList instance, not an XML instance. • When an XMLList instance has one XML instance only, XML methods can be invoked on it; the XMLList instance automatically forwards the invocations to the XML instance. • To obtain an object reference to an XML instance contained by parentNode. childNodeName, you must use the form parentNode.childNodeName[index], even if the XML instance you want is the only item in the XMLList (in which case it is referred to as parentNode.childNodeName[0]). • If an XML element contains text only (and does not contain child elements), con- verting it to a string yields the text it contains, stripped of enclosing tags (e.g., converting Ulysses to a string yields "Ulysses" not " Ulysses"). • If an XML element contains text and contains child elements, converting it to a string yields the element’s source code, complete with tags. For example, converting: Joyce, James to a string yields: "Joyce, James" not: Joyce, James When in doubt, consider using the methods of the XML class to access the content you’re interested in. The explicit names of the XML class’s methods are sometimes easier to understand, though more verbose. Table 18-1. Review: E4X expressions and results Expression Parameter datatype Result novel.AUTHOR String “Joyce, James” novel.AUTHOR XML Type mismatch error (can’t convert XMLList to XML) novel.AUTHOR[0] String “Joyce, James” novel.AUTHOR[0] XML XML instance representing the element oopBook.AUTHOR String String containing XML source code for the four elements oopBook.AUTHOR XMLList XMLList with four XML instances representing the four elements Processing XML with for-each-in and for-in | 377 Processing XML with for-each-in and for-in XML-structured documents often contain data sets that need to be processed system- atically. For example, an XML document might contain population information for the countries of the world, or points on a map, or the costs of items in an order. Whatever the data, the basic approach is the same—each item must be examined and used in some uniform way by the application. In order to make XML-formatted information easy to process, E4X adds a new kind of loop to ActionScript called the for-each-in loop. The for-each-in loop, which we first saw in Chapter 15, provides easy access to the values of an object’s dynamic instance variables or an array’s elements. Recall the generalized syntax for a for-each-in loop: for each (variableOrElementValue in someObject) { statements } We can use the preceding syntaxto process XML instances in an XMLList just as easily as we process an array’s elements or an object’s dynamic instance variables. Example 18-8 demonstrates. The for-each-in loop in Example 18-8 runs three times, once for each child node in the XMLList returned by novel.*. The first time the loop runs, the variable child is assigned a reference to ’s first child node (i.e., the XML instance representing ). The second time the loop runs, child is assigned a reference to <BOOK>’s sec- ond child node (the XML instance representing <AUTHOR>). The third time the loop runs, child is assigned a reference to <BOOK>’s third child node (the XML instance representing <PUBLISHER>). So the output of the loop is: Ulysses Joyce, James Penguin Books Ltd Example 18-9 presents a more involved scenario—calculating the total cost of a cus- tomer order. The comments will guide you through the code. Example 18-8. Using for-in-each to process XML instances var novel:XML = <BOOK ISBN="0141182806"> <TITLE>Ulysses Joyce, James Penguin Books Ltd ; for each (var child:XML in novel.*) { trace(child); } 378 | Chapter 18: XML and E4X Here is the output of the code from Example 18-9: Here is your order: 3 Trinket(s). $9.99 each. 1 Gadget(s). $149.99 each. Example 18-9. Calculating an order total // Create the order. Normally the order would be generated programmatically // in response to user input, but we hard code it for this example. var order:XML = Trinket 9.99 3 Gadget 149.99 1 Toy 39.99 2 // Create a text field in which to display the order details. var outField:TextField = new TextField( ); outField.width = 300; outField.height = 300; outField.text = "Here is your order:\n"; addChild(outField); // Set the initial total cost to 0. var total:Number = 0; // This loop runs once for every element. for each (var item:XML in order.*) { // Display the details for this item in the outField text field. outField.text += item.QUANTITY + " " + item.NAME + "(s)." + " $" + item.PRICE + " each.\n"; // Add the cost of this item to the total cost of the order. // Notice that the quantity and price values are automatically // converted to numbers by the multiplication operation. total += item.QUANTITY * item.PRICE; } // Display the total cost of the order. outField.appendText("TOTAL: " + total); Accessing Descendants | 379 2 Toy(s). $39.99 each. TOTAL: 259.94 Here’s one final example, showing how we can manipulate the order’s content using a for-each-in loop. It assigns the same value to all elements from Example 18-9: // Big SALE! Everything's $1! for each (var item:XML in order.*) { item.PRICE = 1; } We’ll learn more about changing the content of an XML element later in the section “Changing or Creating New XML Content.” Be careful not to mistakenly assume that the XML instances in an XMLList have vari- able names matching their XML element names. Instead, like array elements, the XML instances in an XMLList are arranged in order, and have their numeric posi- tion as variable names. The following code uses a for-in loop to demonstrate. Notice that the variable names are 0, 1, and 2, not “ITEM”. The names 0, 1, and 2 represent each XML instance’s numeric position in the XMLList returned by order.*. for (var childName:String in order.*) { trace(childName); } Output: 0 1 2 For more information on the for-each-in and for-in statements, see Chapter 15. Accessing Descendants We’ve now had plenty of practice accessing the child nodes of an XML element. Next let’s consider how to access not just an element’s child nodes, but also its so- called descendant nodes. An element’s descendants are all the nodes it contains, at any level of the XML hierarchy (i.e., grandchild nodes, great-grandchild nodes and so on). For example, consider the XML fragment in Example 18-10, representing a book and movie loan transaction from a library. Example 18-10. A library loan record var loan:XML = Ulysses Joyce, James 380 | Chapter 18: XML and E4X In the preceding example, the element’s descendants include: • The direct children: and the two elements • The grandchildren: every , <AUTHOR>, <PUBLISHER>, and <DIRECTOR> element • The great-grandchildren: every text node contained by the <TITLE>, <AUTHOR>, <PUBLISHER>, and <DIRECTOR> elements To access an element’s descendants we use the E4X descendant operator (..), which is used as follows: theElement..identifier A descendant-access expression returns an XMLList representing all descendants of theElement whose names match identifier. For example, the following expression yields an XMLList that has two XML instances, representing the two <DIRECTOR> elements from Example 18-10. loan..DIRECTOR Notice that the <DIRECTOR> elements are not direct children of the <LOAN> element; they are grandchildren. The descendant operator gives us direct, easy access to nodes anywhere in an XML hierarchy. For example, to retrieve a list of all <TITLE> elements in the library loan record, we use: loan..TITLE To print the titles of all items being loaned, we can use code such as the following: trace("You have borrowed the following items:"); for each (var title:XML in loan..TITLE) { trace(title); } // Output: You have borrowed the following items: <PUBLISHER>Penguin Books Ltd</PUBLISHER> </BOOK> <DVD ISBN="0790743086" DUE="1136610000000"> <TITLE>2001 A Space Odyssey Stanley Kubrick Warner Home Video Spirited Away Hayao Miyazaki Walt Disney Video Example 18-10. A library loan record (continued) Accessing Descendants | 381 Ulysses 2001 A Space Odyssey Spirited Away That’s super handy! The expression a.b is a list of all direct child elements of a named b; the expression a..b is a list of all descendant elements of a named b. The syntaxis intentionally similar the only difference is the depth of the nodes returned. The descendant operator also works with attributes. To retrieve a list of descendant attributes rather than elements, use the following form: theElement..@attributeName For example, the following expression yields an XMLList that has three XML instances, representing the three DUE attributes from Example 18-10. loan..@DUE Here’s another handy bit of code: trace("Your items are due on the following dates:"); for each (var due:XML in loan..@DUE) { trace(new Date(Number(due))); } // In Eastern Standard Time, the output is: Your items are due: Sun Jan 1 00:00:00 GMT-0500 2006 Sat Jan 7 00:00:00 GMT-0500 2006 Sat Jan 14 00:00:00 GMT-0500 2006 To retrieve an XMLList that includes every single node descending from a given ele- ment, use: theElement..* For example, the following code returns an XMLList with all 21 descendants of the element: loan..* Can you identify all 21 descendants? Example 18-11 presents them all, rendered in comforting ASCII art. Each node’s position in the XMLList returned by loan..* is indicated in parentheses. (You didn’t forget the text nodes, did you? Remember, they count as descendants.) 382 | Chapter 18: XML and E4X To retrieve an XMLList that includes every single attribute defined both on an ele- ment and on all of its descendants, use: theElement..@* For example, the following code returns an XMLList with all attributes defined by descendants of (there are sixtotal). Note that if defined any attributes (it doesn’t), they would be included in the list. loan..@* The following code prints the attributes returned by loan..@* using a for-each-in loop. For each attribute, the code shows the attribute name and value, and the con- tents of its parent’s child element. for each (var attribute:XML in loan..@*) { trace(attribute.parent( ).TITLE + ": " + attribute.name( ) + "=" + attribute); } // Output: Ulysses: ISBN=0141182806 Ulysses: DUE=1136091600000 Example 18-11. The nodes of loan..* BOOK (1) |-TITLE (2) | |-Ulysses (3) | |-AUTHOR (4) | |-Joyce, James (5) | |-PUBLISHER (6) |-Penguin Books Ltd (7) DVD (8) |-TITLE (9) | |-2001 A Space Odyssey (10) | |-AUTHOR (11) | |-Stanley Kubrick (12) | |-DIRECTOR (13) |-Warner Home Video (14) DVD (15) |-TITLE (16) | |-Spirited Away (17) | |-AUTHOR (18) | |- Hayao Miyazaki (19) | |-DIRECTOR (20) |- Walt Disney Video (21) Filtering XML Data | 383 2001 A Space Odyssey: ISBN=0790743086 2001 A Space Odyssey: DUE=1136610000000 Spirited Away: ISBN=078884461X Spirited Away: DUE=1137214800000 To retrieve an XMLList that includes every attribute defined on an element’s descen- dants, but not on the element itself, use: theElement..*.@* or the following more verbose, but less arcane code: theElement..*.attributes( ) In English the preceding code reads “invoke the XMLList class’s instance method attributes( ) on the XMLList representing theElement’s descendants.” The result is an XMLList representing every attribute defined on theElement’s descendants. For a refresher on attributes( ) see the earlier section “Accessing Attributes.” To access attributes or elements whose names contain characters con- sidered illegal in an ActionScript identifier, we must use the XML class’s instance method descendants( ) instead of the descendants oper- ator. The format theElement..["someName"] is not allowed with the descendants operator. The descendants operator is useful on its own, but it becomes indispensable when combined with E4X’s filtering capabilities. Once you understand the descendants operator and E4X filtering, you’ll be able to meet nearly all your XML processing needs quickly and easily. We’ll find out how in the next section. Filtering XML Data The E4X filtering predicate operator is a simple but powerful search tool. It can take any XMLList and return a subset of items from that list based on a specified condi- tion. (The term predicate is borrowed from the W3C’s XPath Language. See http:// www.w3.org/TR/xpath20/#id-predicates.) The filtering predicate operator takes the general form: theXMLList.(conditionExpression) For each item in theXMLList, the conditionExpression is executed once. If the conditionExpression yields true for an item, that item is added to an XMLList that is returned after all items have been processed. Note that during each execution of the conditionExpression, the current item is temporarily added to the front of the scope chain, allowing the item’s child elements and attributes to be referenced directly by name within the expression. 384 | Chapter 18: XML and E4X The filtering predicate operator is extremely intuitive to use. Let’s take a look at a new XML fragment and do some filtering! Example 18-12, the new fragment, repre- sents a company’s staff list. Now for our first filtering operation: suppose we want a list of the employees with James Porter as a manager. We can filter the list of <EMPLOYEE> elements from Example 18-12 like this: // First obtain an XMLList object representing all <EMPLOYEE> elements var allEmployees:XMLList = staff.*; // Now filter the list of <EMPLOYEE> elements var employeesUnderJames:XMLList = allEmployees.(MANAGER == "James Porter"); The expression allEmployees.(MANAGER == "James Porter") returns an XMLList of all items in allEmployees whose <MANAGER> element contains the text “James Porter.” You have to love the simplicity and readability of E4X. Just remember that the pre- ceding line of code works because each item in allEmployees is added to the scope chain each time that (MANAGER == "James Porter") is evaluated. So every time the expression (MANAGER == "James Porter") runs, it has the following conceptual mean- ing, expressed in pseudocode: if (currentEmployee.MANAGER == "James Porter") add currentEmployee to results For comparison, here is some actual ActionScript code that does the same thing as the expression allEmployees.(MANAGER == "James Porter"): Example 18-12. An employee list var staff:XML = <STAFF> <EMPLOYEE ID="501" HIRED="1090728000000"> <NAME>Marco Crawley</NAME> <MANAGER>James Porter</MANAGER> <SALARY>25000</SALARY> <POSITION>Designer</POSITION> </EMPLOYEE> <EMPLOYEE ID="500" HIRED="1078462800000"> <NAME>Graham Barton</NAME> <MANAGER>James Porter</MANAGER> <SALARY>35000</SALARY> <POSITION>Designer</POSITION> </EMPLOYEE> <EMPLOYEE ID="238" HIRED="1014699600000"> <NAME>James Porter</NAME> <MANAGER>Dorian Schapiro</MANAGER> <SALARY>55000</SALARY> <POSITION>Manager</POSITION> </EMPLOYEE> </STAFF> Filtering XML Data | 385 var resultList:XMLList = new XMLList( ); var counter:int = 0; for each (var employee:XML in allEmployees) { if (employee.MANAGER == "James Porter") { resultList[counter] = employee; counter++; } } Let’s look at some more examples that demonstrate how to access the information in Example 18-12 based on a variety of conditions. The following expression returns a list of employees with a salary less than or equal to $35,000. staff.*.(SALARY <= 35000) The next expression returns a list of employees with a salary between $35,000 and $50,000: staff.*.(SALARY >= 35000 && SALARY <= 50000) This expression returns a list of the designers in the company: staff.*.(POSITION == "Designer") This one returns a list of employees whose ID number is 238 (it so happens that there’s only one, but it’s still wrapped in an XMLList instance). staff.*.(@ID == "238") Here we retrieve a list of employees hired in the year 2004 (to represent time, we use the standard milliseconds-from-1970 format used by the Date class): staff.*.(@HIRED >= 1072933200000 && @HIRED <= 1104555600000) Finally, we print the date on which Graham was hired: // In Eastern Standard Time, displays: Fri Mar 5 00:00:00 GMT-0500 2004 trace(new Date(Number(staff.*.(NAME == "Graham Barton").@HIRED))); Fun, ain’t it? Predicates are great! To filter a list where not every item has a given attribute or child ele- ment, we must use hasOwnProperty( ) to check for the existence of that attribute or child before filtering on it. Otherwise, a reference error occurs. For example, the following code returns every element in someDocument that has a color attribute set to “red”: someDocument..*.(hasOwnProperty("@color") && @color == "red") We’ve now seen lots of ways to access various specific nodes or groups of nodes within an XML document. Next up: using tree traversal to access not just some of the nodes in a document, but every node in a document. 386 | Chapter 18: XML and E4X Traversing XML Trees In general programming terms, to traverse means to access every node in a data structure and process it in some way. Tree traversal means using a recursive algo- rithm to traverse the nodes of a tree structure. In DOM-based XML implementations (such as the legacy flash.xml.XMLDocument class), programmers often write custom code to traverse XML trees when searching for information. For example, a human resources program might include traversal code that searches through an XML document looking for all employees classified as managers or all employees in a certain salary bracket. As we saw in the previous sec- tion, such custom tree-traversal code is largely unnecessary in E4X because most searches can be performed using E4X’s descendants and filtering predicate opera- tors. However, there are still some situations in which a tree must be traversed, even in E4X. Fortunately, the E4X code for doing so is trivial. In E4X, we can use the descendants operator in combination with the properties wildcard to retrieve an XMLList containing every descendant node for a given ele- ment, as follows: someElement..* // Returns a list containing // all of someElement's descendants And we can use the for-each-in statement to iterate over every item in an XMLList. By combining these techniques, we can easily traverse every node in an XML tree as follows: for each (var child:XML in someElement..*) { // Process child... } Note, however, that the preceding code does not strictly conform to the classic defi- nition of tree traversal because it never accesses the root of the hierarchy (someElement). In situations that require the root node to be processed along with its children, simply add the root node to another XML instance temporarily, as in: var tempRoot:XML = <root/>; tempRoot.appendChild(someElement); for each (var child:XML in tempRoot..*) { // Process child... } Let’s look at a simple real-world traversal example. Suppose we’re creating a blog posting system that allows users to contribute responses that can include HTML markup. In order to make the responses XHTML-compliant, we want to convert all tag names found in user responses to lowercase. Here’s an example of a response that includes problematic uppercase and mixed case tag names: var message:XML = <message> <B>HEY!</B> I just wanted to say that your site is so cool!! You should <A Href="http://mysiteiscooltoo.com/">visit mine</A> sometime. </message>; Changing or Creating New XML Content | 387 Here’s the tree traversal code that converts every element and attribute name in the preceding XML fragment to lowercase: for each (var child:XML in message..*) { // If the node is an element... if (child.nodeKind( ) == "element") { // ...change its name to lowercase. child.setName(child.name().toString().toLowerCase( )); // If the node has any attributes, change their names to lowercase. for each (var attribute:XML in child.@*) { attribute.setName(attribute.name().toString().toLowerCase( )); } } } Here is the new XML resulting from the preceding code, with all tag and attribute names converted to lowercase: <message> <b>HEY!</b>I just wanted to say that your site is so cool!! You should <a href="http://mysiteiscooltoo.com/">visit mine</a> sometime. </message> We’ve spent most of this chapter exploring how to access content in an existing XML document. Next we’ll consider how to add to and change that content. Changing or Creating New XML Content In E4X, most common additions and modifications to an existing XML instance can be achieved using simple assignment statements. E4X assignments, however, have different results depending on the type of value being assigned and the target of the assignment. Let’s look at the various scenarios one at a time. Changing the Contents of an Element To change the contents of an XML element, we assign that element any value other than an XMLList or XML object. The value is converted to a string and replaces the element’s content. Recall our <BOOK> fragment: var novel:XML = <BOOK ISBN="0141182806"> <TITLE>Ulysses Joyce, James Penguin Books Ltd To change the contents of the element from "Ulysses" to "The Sun Also Rises", we use: novel.TITLE[0] = "The Sun Also Rises"; 388 | Chapter 18: XML and E4X But remember that E4X lets us treat an XMLList like an XML object wherever possi- ble, so because the XMLList returned by novel.TITLE has a single XML instance only, we can use this more convenient code: novel.TITLE = "The Sun Also Rises"; // Removed [0] However, if the XMLList returned by novel.TITLE had more than one <TITLE> ele- ment, the assignment would have a different meaning, as described in the later sec- tion “Assigning Values to an XMLList.” (If you need a refresher on the difference between novel.TITLE[0] and novel.TITLE, see the earlier section, “Treating XMLList as XML, Revisited.”) Now let’s change the author and publisher of the book too: novel.AUTHOR = "Hemingway, Ernest"; novel.PUBLISHER = "Scribner"; Alternatively, the content of an element can be changed using the XML class’s instance method setChildren( ). For example: novel.TITLE.setChildren("The Sun Also Rises"); Changing an Attribute Value To change an XML attribute value, simply assign the attribute any new value in an assignment statement. The new value is converted to a string and then replaces the attribute’s existing value. For example, the following code changes the ISBN attribute value from "0141182806" to "0684800713": novel.@ISBN = "0684800713"; In the preceding assignment, using a string rather than a number as the new value preserves the leading zero. If the value assigned to the attribute is an XMLList containing attributes, the attribute values in the XMLList are concatenated into a single string separated by spaces, which is then assigned to the attribute. This slightly unusual behavior can be used to collect a group of attributes into a single attribute. For example: var books:XML = <BOOKS> <BOOK ISBN="0141182806"/> <BOOK ISBN="0684800713"/> <BOOK ISBN="0198711905"/> </BOOKS>; var order:XML = <ORDER ITEMS=""/>; order.@ITEMS = books.*.@ISBN; // Yields: <ORDER ITEMS="0141182806 0684800713 0198711905"/> Changing or Creating New XML Content | 389 Replacing an Entire Element To replace an XML element with new elements, we assign either an XMLList or XML object to that element. For example, in the following code, the <DIV> element replaces the <P> element: var doc:XML = <DOC> <P ALIGN="CENTER">E4X is fun</P> </DOC>; doc.P = <DIV>E4X is convenient</DIV>; // Yields: <DOC> <DIV>E4X is convenient</DIV> </DOC> The content of an element can also be changed using the XML class’s instance method replace( ). For example: // Same as: doc.P = <DIV>E4X is convenient</DIV> doc.replace("P", <DIV>E4X is convenient</DIV>); Note that when an XML element is replaced by content from another document, the new content is a copy of, not a reference to, the other document’s content. For exam- ple, consider the following two XML fragments: var user1:XML = <USERDETAILS> <LOGIN>joe</LOGIN> <PASSWORD>linuxRules</PASSWORD> </USERDETAILS>; var user2:XML = <USERDETAILS> <LOGIN>ken</LOGIN> <PASSWORD>default</PASSWORD> </USERDETAILS>; We can replace the <PASSWORD> element of user2 with the <PASSWORD> element of user1 as follows: user2.PASSWORD = user1.PASSWORD; After the replacement, the two <PASSWORD> elements have the same content: trace(user1.PASSWORD[0] == user2.PASSWORD[0]); // Displays: true But they do not refer to the same XML instance: trace(user1.PASSWORD[0] === user2.PASSWORD[0]); // Displays: false For information on the difference between the preceding two equality expressions, see the later section “Determining Equality in E4X.” 390 | Chapter 18: XML and E4X Adding New Attributes and Elements We can add new attributes and elements to a document using the same assignment syntax we use to modify and replace existing attributes and elements. In E4X, when a value is assigned to an attribute or element that does not already exist, ActionScript automatically adds the specified attribute or element to the docu- ment. As an example, let’s rebuild our <BOOK> fragment from scratch. We’ll start with an empty <BOOK> element: var novel:XML = <BOOK/>; Next, we add the ISBN attribute: novel.@ISBN = "0141182806"; Finally, we add the <TITLE>, <AUTHOR>, and <PUBLISHER> elements: novel.TITLE = "Ulysses"; novel.AUTHOR = "Joyce, James"; novel.PUBLISHER = "Penguin Books Ltd"; For each assignment, the new element is appended to the <BOOK> tag as its new last child. So the result of the preceding code is the following XML fragment: <BOOK ISBN="0141182806"> <TITLE>Ulysses Joyce, James Penguin Books Ltd Assignment syntaxcan also be used to add a nested XML structure with a single assignment. For example, suppose we want to add the following nested content to the element, describing the novel’s setting: Dublin Ireland To do so, we would use the following code: novel.SETTING.CITY = "Dublin"; novel.SETTING.COUNTRY = "Ireland"; At runtime, when ActionScript executes the first statement, it recognizes that neither the nor the elements are already in the document, and, hence, cre- ates them both. When ActionScript executes the second statement, it sees that the element already exists, and, therefore, doesn’t recreate it. Instead, Action- Script simply adds the element to the existing element. Here’s the resulting XML: Ulysses Joyce, James Penguin Books Ltd Changing or Creating New XML Content | 391 Dublin Ireland We can use a similar approach to represent the setting information in a single ele- ment, of the following format: To do so, we simply assign the desired attributes the desired values, as in: novel.SETTING.@CITY = "Dublin"; novel.SETTING.@COUNTRY = "Ireland"; //Yields: Ulysses Joyce, James Penguin Books Ltd In this section we’ve learned that assigning a value to an element that does not already exist causes that element to be added to the document. But what if we want to add an element by the same name as an existing element? For example, how would we add multiple elements to the element? The answers to that question is covered next. As you read over the following sections, notice the use of the additive operator (+), which creates a new XMLList from a series of XML or XMLList instances. The additive operator takes the form: XMLOrXMLListInstance1 + XMLOrXMLListInstance2 It returns a new XMLList instance that contains a flattened list of all XML instances in XMLOrXMLListInstance1 and XMLOrXMLListInstance2. Adding a new child after all existing children To add a new last child to an existing element, use one of the following techniques: parent.insertChildAfter(parent.*[parent.*.length( )-1], ) or: parent.*[parent.*.length( )-1] = parent.*[parent.*.length( )-1] + or: parent.appendChild() For example, the following code adds a new element to , imme- diately following the existing element: var novel:XML = Ulysses Joyce, James 392 | Chapter 18: XML and E4X Penguin Books Ltd novel.insertChildAfter(novel.*[novel.*.length( )-1], A modern classic); The preceding line is synonymous with the following code, which replaces ’s last child () with an XMLList containing ’s last child () and the element: novel.*[novel.*.length()-1] = novel.*[novel.*.length( )-1] + A modern classic; For the sake of easier comprehension, here’s the preceding line again, in pseudocode: // PSEUDO-CODE: = + A modern classic We can write the same thing more succinctly (in real ActionScript code) as follows: novel.*[novel.*.length( )-1] += A modern classic; But here is the most convenient approach: novel.appendChild(A modern classic); Adding a new child after a specific existing child To add a new child after a specific existing child, use one of the following techniques: parent.insertChildAfter(parent.existingChild[n], ) or: parent.existingChild[n] = parent.existingChild[n] + or: parent.*[childIndex] = parent.*[childIndex] + For example, the following code adds a second element to , immedi- ately following the existing element: novel.insertChildAfter(novel.AUTHOR[0], Dave Luxton); insertChildAfter( ) requires an XML instance (not an XMLList instance!) as its first argument, so we must make direct reference to the XML instance novel.AUTHOR[0]. For a refresher on the difference between XML and XMLList instances, see the earlier section “Treating XMLList as XML, Revisited.” As an alternative to the insertChildAfter( ) approach, we can use the following code: novel.AUTHOR[0] = novel.AUTHOR[0] + Dave Luxton; Or, more succinctly: novel.AUTHOR[0] += Dave Luxton; Changing or Creating New XML Content | 393 Here is yet another synonymous approach: // Add a new XML element after novel's second child novel.*[1] = novel.*[1] + Dave Luxton; Again, the preceding line can be written more succinctly as, novel.*[1] += Dave Luxton; Adding a new child before a specific existing child To add a new child before a specific existing child, use one of these techniques: parent.insertChildBefore(parent.existingChild[n], ) // or parent.existingChild[n] = parent.existingChild[n] + // or parent.*[childIndex] = parent.*[childIndex] + For example, the following code adds a new element to our book, immedi- ately following the first element: novel.insertChildBefore(novel.AUTHOR[0], 19.99); As with insertChildAfter( ), note that insertChildBefore( ) requires an XML instance (not an XMLList instance!) as its first argument. The preceding line is synonymous with: novel.AUTHOR = 19.99 + novel.AUTHOR; Here is yet another synonymous approach: // Add a new XML element before novel's second child novel.*[1] = 19.99 + novel.*[1]; Adding a new child before all existing children To add a new element as the first child of an existing element, use any of the follow- ing techniques: parent.insertChildBefore(parent.*[0], ) // or parent.*[0] = + parent.*[0] // or parent.prependChild() For example, the following code adds a new element to our book, imme- diately preceding the existing element: novel.insertChildBefore(novel.*[0], <PAGECOUNT>1040</PAGECOUNT>); The preceding line is synonymous with: novel.*[0] = <PAGECOUNT>1040</PAGECOUNT> + novel.*[0]; Here is the most convenient approach: novel.prependChild(<PAGECOUNT>1040</PAGECOUNT>); 394 | Chapter 18: XML and E4X Deleting Elements and Attributes To remove an element or attribute from a document, use the delete operator as follows: delete elementOrAttribute For example, the following code removes the ISBN attribute from the <BOOK> element: delete novel.@ISBN; The following code removes the <TITLE> element from the <BOOK> element: delete novel.TITLE; Here’s how to delete all children contained by an element: delete novel.*; // Removes <TITLE>, <AUTHOR>, and <PUBLISHER> // from the original XML fragment. The same technique can be used to remove the text content of an element: delete novel.TITLE.*; // Removes "Ulysses" // from the original XML fragment. Here’s how to delete all attributes of an element: delete novel.@*; // Removes all attributes (in this case, ISBN) References to Parts of a Document Are Not Live As you change or add new content to an XML object, bear in mind that any updates you make will not be reflected by variables that refer to part of that document. For example, the following code creates a variable, children, that a refers to <BOOK>’s child nodes: var novel:XML = <BOOK ISBN="0141182806"> <TITLE>Ulysses Joyce, James Penguin Books Ltd var children:XMLList = novel.*; If we now remove the element, the change is made to the original docu- ment, but is not reflected by the children variable: // Remove delete novel.PUBLISHER; trace(novel); // Displays: // Ulysses // Joyce, James // trace(children); // Displays: Ulysses // Joyce, James // Penguin Books Ltd // is still there! Future versions of E4X may support live references to parts of a document. Changing or Creating New XML Content | 395 Using XML Entities for Special Characters E4X implements special treatment and rules for certain punctuation characters when they appear in an XML literal or XML assignment. Table 18-2 explains how to include these characters in an XML document in ActionScript. The left column of the table lists the characters, while the remaining columns show the code required to include these characters, in four different contexts. For reference, the following code shows an example of each of the four types of contexts (the context is represented by someText): // Text of an attribute literal var xml:XML = // Text assigned to an attribute xml.@someOtherAttribute = "someText" // Text node in an element literal var xml:XML = someText // Text node assigned to an element xml.someOtherElement = "someText"; * In these contexts, the newline sequence \n is automatically converted to the entity . ** To include ' within an attribute value delimited by ', use the escape sequence '. *** The sequence \n can be used if the element value is computed. For example: var val:String = "Newlines \n are \n okay \n here!"; var paragraph:XML =

{val}

; **** Unlike in strings, in an XML literal, the backslash (\) character is never interpreted as the beginning of an escape sequence. Note that although the characters > and & can be used in literal form anywhere in an XML literal, when ActionScript encounters them in a text node while parsing XML, it automatically converts them to the entities > and &, respectively. Likewise, when ActionScript encounters & in an attribute value while parsing XML, it automat- ically converts it to the entity &. However, when used in a string context, those entities will be converted back to their original characters. To view the text node Table 18-2. Assignments of special punctuation characters Character Text of an attribute literal Text assigned to an attribute Text node in an element literal Text node assigned to an element \ \\ \\ \**** \\ & && && " " \" or " " \" ' '** ''' < < < < < > >> >> Newline (\n) Unsupported* Unsupported* Unsupported*,*** \n { { { { { } } } } } 396 | Chapter 18: XML and E4X with its entities intact, use the XML class’s instance method toXMLString( ). The fol- lowing code illustrates: var p:XML =

&>

; trace(p.toString( )); // Displays: &> trace(p.toXMLString( )); // Displays:

&>

Finally, note also that although the ' character can be used to delimit an attribute value in an XML literal, it is converted to the " character during parsing. The following code illustrates: var p:XML =

; trace(p.toXMLString( )); // Displays:

Assigning Values to an XMLList As we learned in the section “Changing the Contents of an Element,” there’s no dif- ference between assigning a value to an XMLList with a single XML instance and assigning that value to the instance directly. However, assigning a value to an XMLList with more than one XML instance can have a variety of different results. Depending on the type of value being assigned and the type of XML instances in the list, the list might be changed or even replaced entirely. Assignment to an XMLList instance has only one typical usage scenario: replacing the children of a parent element with a new XML element or list of elements. For example, the following code replaces ’s two

children with a single

element: var doc:XML =

Errors are your friends

Backup often

; doc.* =

Practice coding everyday

; // Yields:

Practice coding everyday

Assigning a value to an XMLList is uncommon, and therefore, not exhaustively cov- ered in this book. Readers interested in grotesque acts of programming—such as attempting to assign a list of processing instructions to a list of attributes—are left to explore such indecency on their own. Loading XML Data | 397 Loading XML Data For instructive purposes, most XML examples in this chapter have been written in literal form. However, in real applications it’s much more common to load XML from an external source. To load external XML data into an XML instance, follow these general steps: 1. Create a URLRequest object describing the location of the external XML (either a file or a server-side script that returns XML). 2. Create a URLLoader object, and use its load( ) method to load the XML. 3. Wait for the XML to load. 4. Pass the loaded XML to the constructor of a new XML instance. While a full discussion of the URLRequest and URLLoader classes is beyond the scope of this chapter, Example 18-13 demonstrates the code required to load XML data into an XML instance. The class in the example, XMLLoader, extends Sprite so that it can be compiled as an application’s main class for testing. For information on the URLRequest and URLLoader classes see Adobe’s ActionScript Language Refer- ence. For information on event handling, see Chapter 12. Example 18-13. Loading external XML package { import flash.display.*; import flash.events.*; import flash.net.*; // Demonstrates the code required to load external XML public class XMLLoader extends Sprite { // The variable to which the loaded XML will be assigned private var novel:XML; // The object used to load the XML private var urlLoader:URLLoader; // Constructor public function XMLLoader ( ) { // Specify the location of the external XML var urlRequest:URLRequest = new URLRequest("novel.xml"); // Create an object that can load external text data urlLoader = new URLLoader( ); // Register to be notified when the XML finishes loading urlLoader.addEventListener(Event.COMPLETE, completeListener); // Load the XML urlLoader.load(urlRequest); } // Method invoked automatically when the XML finishes loading private function completeListener(e:Event):void { // The string containing the loaded XML is assigned to the URLLoader 398 | Chapter 18: XML and E4X Note that all ActionScript load operations, including that shown in Example 18-13, are subject to Flash Player’s security limitations. For complete information on secu- rity considerations, see Chapter 19. Working with XML Namespaces XML uses namespaces to prevent name conflicts in markup, with the ultimate goal of allowing markup from different XML-based vocabularies to coexist peacefully in a single document. ActionScript supports namespaces both as part of E4X and as a general programming tool. This section describes how to work with namespaces using E4X syntaxbut assumes prior knowledge of the concepts expressedby the W3C definition of namespaces in XML. For an introduction to namespaces in XML, see the following online resources: Ronald Bourret’s “XML Namespaces FAQ”: http://www.rpbourret.com/xml/NamespacesFAQ.htm “Namespaces in XML 1.1” (W3C recommendation): http://www.w3.org/TR/xml-names11/ “Plan to use XML namespaces, Part 1,” by David Marston: http://www-128.ibm.com/developerworks/library/x-nmspace.html For information on the non-XML uses of namespaces in ActionScript programming, see Chapter 17. Accessing Namespace-Qualified Elements and Attributes We’ve already learned how to access elements and attributes not qualified by a namespace. To learn the additional techniques required to access elements and attributes qualified by a namespace, let’s look at a new XML fragment example, shown in Example 18-14. The fragment depicts part of a hypothetical furniture cata- log. As you read the example, pay attention to the following namespace-related items: • The namespace URI http://www.example.com/furniture, and its companion pre- fix shop • The default namespace, http://www.w3.org/1999/xhtml // object's data variable (i.e., urlLoader.data). To create a new XML // instance from that loaded string, we pass it to the XML constructor novel = new XML(urlLoader.data); trace(novel.toXMLString( )); // Display the loaded XML, now converted // to an XML object } } } Example 18-13. Loading external XML (continued) Working with XML Namespaces | 399 • Three elements qualified by the namespace http://www.example.com/furniture: , , and • One attribute qualified by the namespace http://www.example.com/furniture: shop:id Example 18-14 is primarily an XHTML document intended to be rendered by web browsers, but it also contains markup representing items in the furniture catalog. The furniture markup gives the document semantic structure, allowing it to be pro- cessed by clients other than a web browser. The catalog uses a namespace to disam- biguate XHTML markup from furniture markup. As a result, the element can represent, on one hand, a piece of furniture and, on the other, the graphical lay- out of a web page—all without name conflicts. To access the elements and attributes qualified by the namespaces in Example 18-14, we must first obtain a reference to those namespaces. To obtain a reference to the namespace http://www.example.com/furniture, we invoke the XML class’s instance method namespace( ) on the document’s root node, passing the prefix "shop" as an argument. As a result, the namespace( ) method returns a Namespace object repre- senting the namespace http://www.example.com/furniture. We assign that object to the variable shopNS for later use. var shopNS:Namespace = catalog.namespace("shop"); Alternatively, if we know the namespace’s URI, we can create a reference to the Namespace using the Namespace constructor: var shopNS:Namespace = new Namespace("http://www.example.com/furniture"); Example 18-14. Using namespaces in a furniture catalog var catalog:XML = Catalog
Item Price
3-legged Coffee Table 79.99
400 | Chapter 18: XML and E4X To retrieve a reference to the default namespace, we invoke the namespace( ) method on the document’s root node without passing any namespace prefix: var htmlNS:Namespace = catalog.namespace( ); Alternatively, if we know the namespace’s URI, we can create a reference to the default Namespace using the Namespace constructor: var htmlNS:Namespace = new Namespace("http://www.w3.org/1999/xhtml"); The inScopeNamespaces( ) and namespaceDeclarations( ) methods can also be used to access the namespaces in a document. For details see Adobe’s ActionScript Lan- guage Reference. In E4X, XML namespace attributes are not represented as attributes (i.e., cannot be accessed via attributes( ) or someElement.@*). Instead, the namespace declarations for an element are accessed via the XML class’s instance method namespaceDeclarations( ). Once we have a Namespace reference, we can access namespace-qualified elements and attributes, using qualified names, which have the following general format: theNamespace::elementLocalName theNamespace::@attributeLocalName For example, here’s the qualified name of in ActionScript: shopNS::price Notice the use of the name-qualifier operator (::), which separates the namespace name from the local name. Here’s how to access the element , which is qualified by the default namespace (http://www.w3.org/1999/xhtml): catalog.htmlNS::body To access the element , which is a child of , we use: catalog.htmlNS::body.shopNS::table To access the attribute shop:id, we use: catalog.htmlNS::body.shopNS::table.@shopNS::id To access the element we could use this nightmarish code: catalog.htmlNS::body.shopNS::table.htmlNS::table.htmlNS::tr[ 1].htmlNS::td[1].shopNS::price But we’ll sleep a little easier if we take advantage of the descendants operator (..)in two places, as in: catalog..shopNS::table..shopNS::price Still, the repetition of shopNS:: is a bit irritating. We can save some keystrokes by asking ActionScript to automatically qualify all unqualified element and attribute Working with XML Namespaces | 401 names with a namespace of our choosing. To do so, we use the default XML namespace statement, which takes the form: default xml namespace = namespaceOrStringURI For example, the following code causes ActionScript to automatically qualify all unqualified element and attribute names with the namespace http://www.example. com/furniture: default xml namespace = shopNS; Subsequent to issuing that statement, the namespace http://www.example.com/ furniture is implied in all unqualified element and attribute references, so we can reduce this code: catalog..shopNS::table..shopNS::price to this: catalog..table..price It’s like a massage and a hot bath! Due to a bug in Flash Player 9, the preceding example code (catalog..table..price) yields undefined the first time it runs. In a more complete example, the catalog document would likely contain more than one element. To access a specific table we’d have to use the filtering predicate, as in: catalog..table.(@id == 4875)..price Example 18-15 shows the code we would use to access and display information about all of the tables in the catalog. As with element and attribute names, we can use the properties wildcard (*) with namespaces. For example, the following code returns an XMLList representing all elements in all namespaces: catalog..*::table To retrieve all descendants at every level in all namespaces or in no namespace, use: theXMLObj..*::* // elements theXMLObj..@*::* // attributes Example 18-15. Showing the tables in the catalog var shopNS:Namespace = catalog.namespace("shop"); default xml namespace = shopNS; for each (var table:XML in catalog..table) { trace(table..desc + ": " + table..price); } 402 | Chapter 18: XML and E4X To retrieve all children in all namespaces or in no namespace, use: theXMLObj.*::* // elements theXMLObj.@*::* // attributes Creating Namespace-Qualified Elements and Attributes To create elements and attributes that are qualified by namespaces, we combine the qualified-names syntaxcovered in the previous section with the creation techniques covered in the earlier section “Changing or Creating New XML Content.” Before we can create namespace-qualified names, we must create (or obtain) a refer- ence to a Namespace object. For example, the following code creates two Namespace objects and assigns them to the variables htmlNS and shopNS for later use in qualified names: var htmlNS:Namespace = new Namespace("html", "http://www.w3.org/1999/xhtml"); var shopNS:Namespace = new Namespace("shop", "http://www.example.com/furniture"); When creating an entire document rather than a single element or attribute, it’s cus- tomary and convenient to use a default namespace, which is specified using the default XML namespace statement. For example, the following code sets the default namespace to http://www.w3.org/1999/xhtml: default xml namespace = htmlNS; Once the default namespace has been established, all subsequently created elements (but not attributes) without an explicit namespace are automatically qualified by the default namespace. For example, the following code creates an element with the local name “html”; it has no explicit namespace, so it is automatically qualified by the default namespace (http://www.w3.org/1999/xhtml): var catalog:XML = ; The XML source code generated by the previous line is: To add a namespace declaration to a given element, we use the XML class’s instance method addNamespace( ). For example, the following code adds a new namespace declaration to the preceding element: catalog.addNamespace(shopNS); The resulting XML source code is: You probably recognize the preceding element as the first line of code in the catalog document from Example 18-14. Let’s build the rest of that document. Here are the and tags. Their names are automatically qualified by the default namespace (http://www.w3.org/1999/xhtml). Working with XML Namespaces | 403 catalog.head.title = "Catalog"; Next up, the <shop:table> element and its shop:id attribute. Both of those items have names qualified by the namespace http://www.example.com/furniture. The XML source code we want to generate looks like this: <shop:table shop:id="4875"> The ActionScript code we use to generate it is: catalog.body.shopNS::table = ""; catalog.body.shopNS::table.@shopNS::id = "4875"; The preceding code should be very familiar. Except for the namespace qualifier syntax, shopNS::, it’s identical to the code we used earlier to create elements and attributes. The namespace qualifier simply specifies the namespace for the local names table and id. Example 18-16 uses the same technique to generate the rest of the catalog document. In the example, notice the following line of code: catalog.body.shopNS::table.table.tr.td[1] = "Price"; That line creates a new element named “td” immediately following the existing ele- ment at catalog.body.shopNS::table.table.tr.td[0]. Example 18-16. Creating the furniture catalog // Create the namespaces var htmlNS:Namespace = new Namespace("html", "http://www.w3.org/1999/xhtml"); var shopNS:Namespace = new Namespace("shop", "http://www.example.com/furniture"); // Set the default namespace default xml namespace = htmlNS; // Create the root element var catalog:XML = <html/>; // Add the furniture namespace to the root element catalog.addNamespace(shopNS); // Create the remainder of the document catalog.head.title = "Catalog"; catalog.body.shopNS::table = ""; catalog.body.shopNS::table.@shopNS::id = "4875"; catalog.body.shopNS::table.table = ""; catalog.body.shopNS::table.table.@border = "1"; catalog.body.shopNS::table.table.tr.td = "Item"; catalog.body.shopNS::table.table.tr.td[1] = "Price"; catalog.body.shopNS::table.table.tr.@align = "center"; catalog.body.shopNS::table.table.tr[1] = ""; catalog.body.shopNS::table.table.tr[1].@align = "left"; catalog.body.shopNS::table.table.tr[1].td.shopNS::desc = "3-legged Coffee Table"; catalog.body.shopNS::table.table.tr[1].td[1] = ""; catalog.body.shopNS::table.table.tr[1].td[1].shopNS::price = "79.99"; 404 | Chapter 18: XML and E4X We’ve now finish studying all of the major topics in E4X. The remainder of this chapter covers two supplementary subjects: XML conversion and equality. Converting XML and XMLList to a String As we’ve seen throughout this chapter, E4X implements custom rules for converting XML and XMLList instances to a string. For reference and review, this section describes E4X’s XML-to-string and XMLList-to-string conversion rules. Remember that an XML instance can represent five different kinds of content: an element, an attribute, a text node, a comment, or a processing instruction. We’ll consider the conversion rules for each kind separately, but we’ll start with XMLList-to-string con- version in preparation for the subsequent discussion. Converting XMLList to a String When an XMLList has only one XML instance, the result of XMLList’s toString( ) is exactly the same as the result of calling toString( ) on that one instance. For example, in the following code, the title variable refers to an XMLList whose single XML instance represents the <TITLE> element. Converting title to a string yields Ulysses, exactly as if toString( ) had been invoked on the single XML instance directly: var novel:XML = <BOOK ISBN="0141182806"> <TITLE>UlyssesJoyce, JamesPenguin Books Ltd // Create an XMLList with only one XML instance var title:XMLList = novel.TITLE; // Convert the XMLList to a string, and display that string. trace(title); // Displays: Ulysses When an XMLList has more than one XML instance, XMLList’s toString( ) returns the result of calling toXMLstring( ) on each XML instance and concatenating those strings together, each on its own line. For example, in the following code, the XMLList object assigned to details has three XML instances representing the three elements , <AUTHOR>, and <PUBLISHER>: // Create an XMLList with three XML instances var details:XMLList = novel.*; Converting details to a string yields the source XML code for <TITLE>, <AUTHOR>, and <PUBLISHER>: // Convert the XMLList to a string, and display that string trace(details); // Displays: // <TITLE>Ulysses // Joyce, James // Penguin Books Ltd Converting XML and XMLList to a String | 405 Converting an XML Element to a String For XML instances that represent elements, XML’s toString( ) has one of two results, depending on the content of that element. If an element contains child elements, then XML’s toString( ) returns XML source code for the element and its children, for- matted according to the settings of XML.ignoreWhitespace, XML.prettyPrinting, and XML.prettyIndent. For example, in the following code the element has three child elements (, <AUTHOR>, and <PUBLISHER>): var novel:XML = <BOOK ISBN="0141182806"> <TITLE>Ulysses Joyce, James Penguin Books Ltd ; Because the element has child elements, converting it to a string yields XML source code: trace(novel.toString( )); // Displays: // // Ulysses // Joyce, James // Penguin Books Ltd // ; For an element that contains no child elements, XML’s toString( ) returns the text contained by that element, omitting the element’s start and end tags. For example, the following code converts the element to a string. The result is Ulysses, not <TITLE>Ulysses. trace(novel.TITLE.toString( )); // Displays: Ulysses If we want to retrieve a string including the text node and its containing tags, we use XML’s toXMLString( ), as in: trace(novel.TITLE.toXMLString( )); // Displays: Ulysses Notice how E4X’s string conversion rules for XML elements change the way leaf text nodes are accessed in ActionScript. In ActionScript 1.0 and ActionScript 2.0, text nodes were accessed using the variable XML class’s instance variable firstChild (which, in ActionScript 3.0, is now the XMLDocument class’s instance variable firstChild). For example, the legacy equivalent to the E4X statement: trace(novel.TITLE.toString( )); would be: trace(novel.firstChild.firstChild.firstChild); In E4X, the text of an element that contains no child elements can be accessed directly via its containing element’s name when used in a string context. Here are two more E4X examples (this time, we’ve omitted the explicit call to toString( ) 406 | Chapter 18: XML and E4X because ActionScript automatically invokes toString( ) on any argument passed to trace( )): trace(novel.AUTHOR); // Displays: Joyce, James trace(novel.PUBLISHER); // Displays: Penguin Books Ltd And here is a direct comparison between legacy text node access and E4X text node access: // E4X text node access var msg:XML = World J. Programmer Hello trace(msg.TO); // Displays: World trace(msg.FROM); // Displays: J. Programmer trace(msg.MESSAGE); // Displays: Hello // Legacy text node access var msgDoc:XMLDocument = new XMLDocument("" + "World" + "J. Programmer" + "Hello" + ""); trace(msgDoc.firstChild.firstChild.firstChild); // Displays: World trace(msgDoc.firstChild.childNodes[1].firstChild); // Displays: // J. Programmer trace(msgDoc.firstChild.childNodes[2].firstChild); // Displays: Hello Converting an Attribute to a String For XML instances that represent attributes, XML’s toString( ) returns the attribute value only, not the entire attribute definition. For example, the following code con- verts the preceding element’s ISBN attribute to a string. The result is 0141182806 not ISBN='0141182806'. trace(novel.@ISBN.toString( )); // Displays: 0141182806 Converting Comments and Processing-Instructions to Strings When XML’s toString( ) is called on an XML instance that represents a comment or a processing instruction, the entire comment or processing instruction is returned: XML.ignoreComments = false; XML.ignoreProcessingInstructions = false; // Create an XML fragment that contains both a comment and a processing // instruction (shown in bold) var novel:XML = Ulysses Determining Equality in E4X | 407 Joyce, James Penguin Books Ltd ; // Convert the comment to a string. // Displays: trace(novel.comments()[0].toString( )); // Convert the processing instruction to a string. // Displays: trace(novel.processingInstructions()[0].toString( )); Determining Equality in E4X The following sections describe ActionScript’s special rules for determining the equality of XML, XMLList, QName, and Namespace objects. Note, however, that the following sections apply to the equality operator (==) only, not to the strict equality (===) operator. E4X does not modify the semantics of the strict equality operator. Specifically, the strict equality operator considers two instances of XML, XMLList, QName (qualified name), or Namespace equal if, and only if, they point to the same object reference. XML Equality Two XML instances representing elements are considered equal by the equality oper- ator (==) if the XML hierarchy they represent is identical. For example, in the follow- ing code, the variables x1 and x2 point to different object references but are considered equal because they represent the same XML hierarchy. var x1:XML = ; var x2:XML = ; trace(x1 == x2); // Displays: true By default, E4X ignores whitespace nodes, so two XML instances representing ele- ments are considered equal when they have the same markup even if they have dif- ferent formatting. For example, in the following code the XML source code for the XML instance in x1 contains no whitespace nodes, while the XML source code for the XML instance in x2, contains two whitespace nodes; despite this difference, the instances are still considered equal because the whitespace is ignored, so the XML hierarchies are the same. var x1:XML = ; var x2:XML = ; trace(x1 == x2); // Still displays: true However, if we force ActionScript not to ignore whitespace nodes prior to parsing, then the XML instances will not be considered not equal, as shown next: 408 | Chapter 18: XML and E4X XML.ignoreWhitespace = false; // Don't ignore whitespace nodes var x1:XML = ; var x2:XML = ; trace(x1 == x2); // Now displays: false An XML instance representing an element is considered equal to an XML instance representing an attribute if the element contains no child elements and the text con- tained by the element matches the attribute value. For example, in the following code, the QUANTITY attribute is considered equal to the element because has no child elements and contains text that matches QUANTITY’s value: var product:XML = 1; trace(product.@QUANTITY == product.COST); // Displays: true Similarly, an XML instance representing an element is considered equal to an XML instance representing a text node if the element contains no child elements, and the text it contains matches the text node’s value. For example, in the following code, the text node contained by is considered equal to the element because has no child elements and contains text that matches the ’s child text node’s value: var product:XML = 1 1 ; trace(product.COST.*[0] == product.QUANTITY); // Displays: true In all other cases, if the node kind of two XML instances is different, the two are not considered equal. If the node kind is the same, the two are considered equal if: • The node kind is “attribute,” and the attribute values are the same. • The node kind is “text,” and the text of the node is the same. • The node kind is “comment,” and the text between the comments start and end delimiters () is the same. • The node kind is “processing-instruction,” and the text between the processing instruction start and end delimiters () is the same. XMLList Equality To determine whether two XMLList instances are equal, ActionScript compares each of the instances they contain, in order, using the rules for XML equality discussed in the preceding section. If any item in the first XMLList instance is considered not equal to any corresponding item in the second XMLList instance, then the two XMLList instances are not equal. For example, in the following code the XMLList returned by msg1.* is considered equal to msg2.* because each XML instance in msg1.* is equal to an XML instance in the corresponding position in msg2.*: Determining Equality in E4X | 409 var msg1:XML = World J. Programmer Hello ; var msg2:XML = World J. Programmer Hello ; trace(msg1.* == msg2.*); // Displays: true A comparison between an XML instance and an XMLList with only one XML instance is treated as a direct comparison between the two XML instances: trace(msg1.FROM == msg2.*[1]); // Displays: true This means that the equality operator (==) considers an XMLList containing only one XML instance equal to that instance! trace(msg1.FROM == msg1.FROM[0]); // Displays: true To distinguish an XMLList containing only one XML instance from the instance it contains, use the strict equality operator (===): trace(msg1.FROM === msg1.FROM[0]); // Displays: false QName Equality The QName class represents an element or attribute name qualified by a namespace. Two QName instances are considered equal if their namespace name and local names both match (i.e., if they have identical values for the uri and localName vari- ables). For example, the following code creates a QName object using the QName constructor and compares it to a QName object retrieved from an XML document. The two QName objects have the same namespace name and local name, so they are considered equal. var product:XML = 99.99 ; var someCorp:Namespace = product.namespace("someCorp"); var qn1:QName = new QName("http://www.example.com/someCorp", "PRICE"); var qn2:QName = product.someCorp::PRICE.name( ); trace(qn1 == qn2); // Displays: true 410 | Chapter 18: XML and E4X Namespace Equality The Namespace class represents the qualifier part of a qualified name. Two Namespace objects are considered equal if, and only if, they have the same namespace name (i.e., if their uri variables have the same value), regardless of their prefix. For example, the following code creates a Namespace object using the Namespace constructor and compares it to a Namespace object retrieved from an XML document. The two Namespace objects have the same URI, so they are consid- ered equal, despite the fact that they have different prefixes. var product:XML = 99.99 ; var ns1:Namespace = product.namespace("someCorp"); var ns2:Namespace = new Namespace("sc", "http://www.example.com/someCorp"); trace(ns1 == ns2); // Displays: true More to Learn This chapter has covered the majority of E4X’s core functionality, but completely exhaustive coverage is beyond the scope of this book. For further study, see the methods and variables of the XML an XMLList classes in Adobe’s ActionScript Lan- guage Reference. For deep technical details, consider reading the E4X specification at http://www.ecma-international.org/publications/standards/Ecma-357.htm. Up next, we’ll explore Flash Player security restrictions. If researching Flash Player security isn’t your idea of a good time, you might want to consider skipping ahead to Part II, where we’ll learn how to display things on screen. Just remember that Chapter 19 is there to help if you find yourself faced with security errors during development. 411 Chapter 19 CHAPTER 19 Flash Player Security Restrictions20 To protect data from being transferred to unauthorized destinations without appro- priate permission, Flash Player scrutinizes all requests to load or access external resources, or interact with other .swf files or HTML files. Each request a .swf file makes for an external resource (a resource not compiled into the .swf file making the request) is rejected or approved based on the following factors: • The ActionScript operation used to access the resource • The security status of the .swf file performing the request • The location of the resource • The explicit access-permissions set for the resource as determined by either the resource’s creator or distributor • The explicit access-permissions granted by the user (e.g., permission to connect to the user’s camera or microphone) • The type of Flash Player running the .swf file (e.g., plug-in version, standalone version, Flash authoring tool test version) In the preceding list, and throughout this chapter, the following terms have the fol- lowing meanings: Resource distributor The party that delivers a given resource. Typically a server operator such as a web site administrator or socket server administrator. Resource creator The party that actually authors the resource. For .swf files, the resource creator is the ActionScript developer that compiles the .swf. User The user of the computer on which Flash Player is running. This chapter explains Flash Player security restrictions in general terms, and then explores how security specifically affects loading content and accessing external data. 412 | Chapter 19: Flash Player Security Restrictions This chapter covers security restrictions in one specific Flash runtime: Flash Player (both the web browser add-on, and standalone player ver- sions). For information on security limitations imposed by other Flash runtimes (e.g., Adobe AIR and Flash Lite), see Adobe’s documentation. What’s Not in This Chapter Before we start, let’s be clear: security is a deep topic. Complete coverage of Flash Player security is beyond the scope of this book. Moreover, this chapter covers secu- rity features designed to protect users of Flash content in general but does not dis- cuss the development of secure applications such as e-commerce web sites. For much more information on security—including secure-application development topics such as using Secure Sockets Layer (SSL), coding custom encryption algorithms, and guarding data streamed over RTMP—see the following key resources: • Adobe’s documentation, under Programming ActionScript 3.0 ➝ Flash Player APIs ➝ Flash Player Security • Adobe’s Security Topic Center at http://www.adobe.com/devnet/security/ • Adobe’s security white paper at: http://www.adobe.com/go/fp9_0_security • Deneb Meketa’s “Security Changes in Flash Player 8,” which primarily covers local security, at: http://www.adobe.com/devnet/flash/articles/fplayer8_security.html • Deneb Meketa’s “Security Changes in Flash Player 7,” which primarily covers policy files, at: http://www.adobe.com/devnet/flash/articles/fplayer_security.html • Adobe’s Flash Player Help, which covers security settings available to users, at: http://www.adobe.com/support/documentation/en/flashplayer/help/index.html Now let’s explore how Flash Player security affects loading content and accessing external data. The Local Realm, the Remote Realm, and Remote Regions As we’ll see throughout this chapter, ActionScript often bases security restrictions on the locations of .swf files and external resources. When evaluating a location from a security perspective, ActionScript makes a distinction between resources in remote locations and resources in local locations. In this chapter, we’ll use the term remote realm when referring to the logical group of all possible remote locations, such as the Internet. Correspondingly, we’ll use the term local realm when referring to the logi- cal group of all possible local locations. A local location is any location that the user of the computer on which Flash Player is running can access using either the file: Security-Sandbox-Types | 413 protocol (typically used to access the local filesystem) or a universal naming conven- tion (UNC) path (typically used to access computers on a local area network). The remote realm is, itself, further divided into distinct regions delimited conceptu- ally by resource distributor. We’ll call these distributor-delimited regions remote regions. Specifically, a remote region is any one of the following: • An Internet domain • An Internet subdomain • An IP address that points to a computer in the remote realm Hence, according to the preceding list: • sitea.com is a different remote region than siteb.com • games.example.com is a different remote region than finances.example.com • 192.150.14.120 is a different remote region than 205.166.76.26 • 192.150.14.120 is a different remote region than adobe.com, even though 192.150.14.120 resolves to adobe.com (because Flash Player considers numerically specified IP addresses distinct from their equivalent domain names) The terms remote realm, local realm, and remote region are not cur- rently part of Adobe’s official security vocabulary. They are used by this book for expository purposes only. Security-Sandbox-Types ActionScript assigns a security status known as a security-sandbox-type to every .swf file opened by or loaded into Flash Player. There are four possible security-sandbox- types: remote, local-with-filesystem, local-with-networking, and local-trusted. Each security-sandbox-type defines a distinct set of rules that governs a .swf file’s ability to perform external operations. Specifically, the types of external operations a security- sandbox-type can potentially prohibit include: • Loading content • Accessing content as data • Cross-scripting • Loading data • Connecting to a socket • Sending data to an external URL • Accessing the user’s camera and microphone • Accessing local shared objects • Uploading or downloading files selected by the user 414 | Chapter 19: Flash Player Security Restrictions • Scripting an HTML page from a .swf file and vice versa • Connecting to a LocalConnection channel In this chapter, we’ll see how each security-sandbox-type governs the first five of the preceding types of external operations. To learn how security-sandbox-types govern the remaining types of external operations, see Adobe’s documentation, under Programming ActionScript 3.0 ➝ Flash Player APIs ➝ Flash Player Security. Note that when an operation fails due to Flash Player security restrictions, ActionScript either generates a SecurityError or dispatches a SecurityErrorEvent.SECURITY_ERROR event. For details on handling security error conditions, see the section “Handling Security Violations,” near the end of this chapter. How Security-Sandbox-Types Are Assigned To determine a given .swf file’s security-sandbox-type, ActionScript first considers the location from which the .swf file was loaded or opened. All .swf files from the remote realm are assigned the security-sandbox-type remote. By contrast, .swf files from the local realm are assigned one of the remaining three security-sandbox- types—local-trusted, local-with-networking,orlocal-with-filesystem. The specific security-sandbox-type assigned to a local .swf file depends on two factors: • Whether the .swf file was compiled with network support (see the section “Choosing a Local Security-Sandbox-Type,” later in this chapter) • Whether the .swf file is explicitly trusted (a .swf file is said to be explicitly trusted if it is opened from a trusted local location; see the section “Granting Local Trust,” later in this chapter) All .swf files from the local realm that are explicitly trusted are assigned the security- sandbox-type local-trusted. Likewise, executable projector files (i.e., standalone files containing a .swf file and a particular version of Flash Player) are always trusted. All .swf files from the local realm that are not explicitly trusted are assigned either the security-sandbox-type local-with-networking (for .swf files compiled with network support) or the security-sandbox-type local-with-filesystem (for .swf files compiled without network support). For brevity in this chapter, we’ll refer to .swf files whose security-sandbox-type is remote as remote .swf files. Likewise, we’ll use the terms local-with-filesystem .swf file, local-with-networking .swf file, and local-trusted .swf file when referring to .swf files whose security-sandbox-type is local-with-filesystem, local-with-networking, and local-trusted, respectively. As a rule, local-with-networking .swf files have more access to the remote realm than to the local realm. By contrast, local-with-filesystem .swf files have more access to the local realm than to the remote realm. Security Generalizations Considered Harmful | 415 To check a .swf file’s security-sandbox-type at runtime, retrieve the value of the flash.system.Security.sandboxType variable from within that .swf file. Because Flash Player assigns all .swf files from the remote realm a security-sandbox- type of remote, developers creating .swf content for the Web must always work within the limitations of the remote security-sandbox-type. By contrast, developers creating .swf content intended to be loaded or opened locally can use compiler set- tings, configuration files, installers, and instructions to choose between the three local security-sandbox-types. By choosing a security-sandbox-type, developers creat- ing local .swf content effectively select a logical set of external-access capabilities for their content. In the upcoming sections we’ll explore how security-sandbox-types govern a .swf file’s external-access capabilities, then we’ll take a closer look at the mechanisms for, and rationale behind, selecting from among the three local security- sandbox-types. Security Generalizations Considered Harmful Over the remainder of this chapter, we’ll study specific operations and specific secu- rity limitations in precise detail. When describing security rules, this book is careful not to generalize at the expense of the accuracy because security generalizations are often the source of frustrating misconceptions. During your study of Flash Player security, you should likewise be wary of forming overly general impressions. Be mindful that when documentation or third-party resources generalize about Flash Player security, they could be underemphasizing important exceptions. For exam- ple, the following statement is mostly true, and, therefore, makes a tempting general- ization: A .swf file whose security-sandbox-type is local-with-filesystem has full access to the local realm. However, there are many notable exceptions to that statement, including: • local-with-filesystem .swf files cannot connect to sockets. • local-with-filesystem .swf files cannot load local-with-networking .swf files. • local-with-filesystem .swf files cannot access the data of local-trusted .swf files without creator permissions. • Accessing the user’s camera and microphone requires the user’s permission. • Users can disable or limit any .swf file’s ability to store data in local shared objects. In order to avoid confusion, when you face a security issue in your development, always focus on specifics. Determine the specific operation you wish to perform, this security-sandbox-type of your .swf file, and the specific limitations that security- 416 | Chapter 19: Flash Player Security Restrictions sandbox-type imposes on the operation you are performing. Once you have this information, you can confidently decide how to work within or around any security limitations. This chapter does not cover every single security limitation imposed by ActionScript. To determine the limitations ActionScript places on any operation not covered in this chapter, consult that operation’s entry in Adobe’s ActionScript Language Reference. Now let’s explore how each of the four security-sandbox-types govern loading con- tent, accessing content as data, cross-scripting, and loading data. Restrictions on Loading Content, Accessing Content as Data, Cross-Scripting, and Loading Data Most developers encounter ActionScript’s security system for the first time when an operation they expect to succeed is blocked for security reasons. In this section, we’ll study four of the most-often blocked external operations: loading content, accessing content as data, cross-scripting, and loading data. After defining each, we’ll look at the circumstances under which these common operations are blocked. Loading Content Loading content means retrieving any external resource in order to subsequently dis- play or play it. Conceptually, loading-content operations enable developers to present external content to the user, even in cases where ActionScript’s security rules restrict programmatic access to that content’s data. The ActionScript methods considered to be “loading-content” operations from a security perspective are listed in the leftmost column of Table 19-1. For convenience, this chapter occasionally uses the term content resources when referring to resources loaded using one of the methods listed in Table 19-1. Note, however, that it is the specific method used to load the resource—not the file type of the resource—that makes an external operation a loading-content operation. For example, loading a JPEG using the Loader class’s instance method load( ) is consid- Table 19-1. Content-loading operations Content-loading method Type of content Specific file formats supported by Flash Player 9 flash.display.Loader.load( ) Image, Adobe Flash JPEG, GIF, PNG, SWF flash.media.Sound.load( ) Audio MP3 flash.net.NetStream.play( ) Progressive Video FLV Restrictions on Loading Content, Accessing Content as Data, Cross-Scripting, and Loading Data | 417 ered a loading-content operation, but loading the very same JPEG over a binary socket or using the URLLoader class’s instance method load( ) is not considered a content load operation. The distinction is important because different security rules apply to different categories of operations. Accessing Content as Data Accessing content as data means reading the internal information of a content resource—for example, reading the pixels of a bitmap or the spectrum of a sound. Table 19-2 presents the ActionScript methods considered “accessing-content-as-data” operations from a security perspective. Cross-Scripting Cross-scripting means accessing a loaded .swf file programmatically. Many Action- Script operations can be used to cross-script a .swf file, including, but not limited to: • Using the Loader class’s instance variable content to retrieve the object repre- senting the loaded .swf file • Accessing the loaded .swf file’s variables • Calling the loaded .swf file’s methods • Referencing a class defined by the loaded .swf file • Using the BitmapData class’s instance method draw( ) to copy the loaded .swf file’s pixels to a BitmapData object Other cross-scripting operations can be found in Adobe’s ActionScript Language Reference, which explicitly notes any security restrictions that apply to each Action- Script operation. Loading Data In a general sense, the term “loading data” could be used to describe a wide variety of Flash Player load operations, including downloading files from a server via the FileReference class’s instance method download( ), loading objects with Flash Remot- Table 19-2. Accessing content as data, example operations Operation Description Access an image via the Loader class’s instance variable content Retrieve the ActionScript Bitmap object representing a loaded image Invoke the BitmapData class’s instance method draw( ) Copy the pixels of a display asset to a BitmapData object Invoke the SoundMixer class’s instance method computeSpectrum( ) Copy the current sound wave data to a ByteArray Access the Sound class’s instance variable id3 Read a sound’s ID3 metadata 418 | Chapter 19: Flash Player Security Restrictions ing, loading binary data over a Socket object, and so on. However, for the purposes of the current discussion (and the remainder of this chapter), loading data means either: • Loading external text, binary data, or variables using the URLLoader class’s instance method load( ) • Loading data using the URLStream class’s instance method load( ) To determine the limitations ActionScript places on data load opera- tions not covered in this chapter, consult Adobe’s ActionScript Lan- guage Reference. For the URLLoader class’s instance method load( ), the format of the loaded data (text, binary, or variables) is determined by the URLLoader class’s instance variable dataFormat variable. Typical text file formats include XML, TXT, and HTML. Typi- cal binary data formats include images, .swf files, and serialized objects encoded in ActionScript Message Format (AMF); however, binary data can be any file or con- tent loaded into a ByteArray for processing in raw binary format. Variables come in one format only: URL-encoded variables loaded as name/value pairs from an exter- nal text file or script. Note that, similar to content loading, the specific method used to load the resource—not the file type of the resource—makes an external operation a loading- data operation. For example, loading a .swf file using URLLoader’s load( ) method is considered a loading-data operation; loading that same .swf file using Loader’s load( ) method is considered a loading-content operation. Restrictions on Loading Content, Accessing Content as Data, Loading Data, and Cross-Scripting Now that we understand specifically what constitutes loading content, accessing content as data, cross-scripting, and loading data, let’s look at how each of Flash Player’s four security-sandbox-types limits those operations. The upcoming four tables—Tables 19-3, 19-4, 19-5, and 19-6—catalog the circum- stances under which each security-sandbox-type allows and prohibits loading con- tent, accessing content as data, cross-scripting, and loading data. Each table presents the specific regulations enforced by a single security-sandbox-type, indicating whether the external operations listed in the leftmost column are allowed or prohib- ited when used to access the resources listed in the remaining columns. As indicated in the tables, some operations are allowed by creator or distributor permission only. Creator permission means a .swf file contains the appropriate call to the Security class’s static method allowDomain( ) (or, in rare cases, to allowInsecureDomain( )). Distributor permission means the resource distributor has made the appropriate Restrictions on Loading Content, Accessing Content as Data, Cross-Scripting, and Loading Data | 419 cross-domain policy file available. For more information, see the sections “Creator Permissions (allowDomain( ))” and “Distributor Permissions (Policy Files),” later in this chapter. As Table 19-3 through Table 19-6 reveal, loading content is allowed in more situa- tions than accessing content as data, cross-scripting, or loading data. For example, an application might be permitted to load and display a bitmap image but be denied access to that image’s underlying pixel data. Likewise, an application might be per- mitted to load and display an external .swf file but require permission to cross-script that .swf file. Note that Tables 19-3 through 19-6 cover permissions for a single direction of com- munication only, a .swf file loads or accesses an external resource. The tables do not cover the reverse direction of communication in which a loaded .swf file communi- cates with the .swf file that loaded it. For information on bidirectional communica- tion between .swf files, see Adobe’s documentation, under Programming ActionScript 3.0 ➝ Flash Player APIs ➝ Flash Player Security ➝ Cross-scripting. Table 19-3 lists the regulations imposed by the remote security-sandbox-type. In the table, the phrase “.swf ’s region of origin” means the remote region from which the .swf file was opened or loaded. For example, if hiscores.swf is loaded from the remote location http://coolgames.com/hiscores.swf, then hiscores.swf’s region of ori- gin is coolgames.com (for details on remote regions, see the earlier section “The Local Realm, the Remote Realm, and Remote Regions”). Table 19-3 illustrates the following key remote security-sandbox-type rules: • Loading-content, accessing-content-as-data, cross-scripting, and loading-data operations cannot be used with resources from the local realm. • All resources from the entire remote realm can be loaded as content. • Loading-content, accessing-content-as-data, cross-scripting, and loading-data operations can be used with all resources from the .swf file’s region of origin. • Remote realm resources outside the .swf ’s region of origin can be accessed as data or loaded as data if the appropriate distributor permission is granted. • Remote realm .swf files outside the .swf ’s region of origin can be cross-scripted if the appropriate creator permission is granted. Table 19-3. Remote sandbox, selected authorized, and prohibited operations Operation Local realm Remoterealmresourcesfrom .swf ’s region of origin Remote realm resources outside .swf ’s region of origin Loading content Prohibited Allowed Allowed Accessing content as data Prohibited Allowed Allowed by distributor permission only Cross-scripting Prohibited Allowed Allowed by creator permission only 420 | Chapter 19: Flash Player Security Restrictions Table 19-4 lists the regulations imposed by the local-with-filesystem security-sand- box-type. The table illustrates the following key local-with-filesystem security-sand- box-type rules: • Loading-content, accessing-content-as-data, cross-scripting, and loading-data operations cannot be used with resources from the remote realm. • Loading-content, accessing-content-as-data, and loading-data operations can be used with all non-.swf resources from the local realm. • Loading local-with-networking .swf files is strictly prohibited. • Loading and cross-scripting other local-with-filesystem .swf files is allowed. • Cross-scripting local-trusted .swf files requires creator permission. Table 19-5 lists the regulations imposed by the local-with-networking security- sandbox-type. The table illustrates the following key local-with-networking security- sandbox-type rules: • Loading-content operations can be used with resources from the remote realm. • Loading-data and accessing-content-as-data operations can be used with remote- realm resources if the appropriate distributor permission is granted. • Loading-content operations can be used with non-.swf resources from the local realm. • Loading-data and accessing-content-as-data operations cannot be used with resources from the local realm. • Loading local-with-filesystem .swf files is strictly prohibited. Loading data Prohibited Allowed Allowed by distributor permission only Table 19-4. Local-with-filesystem sandbox, selected authorized, and prohibited operations Operation Non-.swf resources in the local realm local-with- filesystem .swf files local-with- networking .swf files local-trusted .swf files Remote- realm resources Loading content Allowed Allowed Prohibited Allowed Prohibited Accessingcontentas data Allowed n/a n/a n/a Prohibited Cross-scripting n/a Allowed Prohibited Allowed by creator permis- sion only Prohibited Loading data Allowed n/a n/a n/a Prohibited Table 19-3. Remote sandbox, selected authorized, and prohibited operations (continued) Operation Local realm Remoterealmresourcesfrom .swf ’s region of origin Remote realm resources outside .swf ’s region of origin Restrictions on Loading Content, Accessing Content as Data, Cross-Scripting, and Loading Data | 421 • Loading and cross-scripting other local-with-networking .swf files is allowed. • Cross-scripting local-trusted .swf files or remote .swf files requires creator permission. Table 19-6 lists the regulations imposed by the local-trusted security-sandbox-type. The table illustrates the only local-trusted security-sandbox-type rule: • Loading-content, accessing content as data, cross-scripting, and loading-data operations can be used with any resource from both the local and remote realms The local trusted security-sandbox-type gives a .swf file the greatest possible level of freedom Flash Player offers. We’ve now seen how four types of operations are regulated by each of the four security-sandbox-types. Before we move to other security topics, let’s look at one last type of operation: connecting to a socket. Table 19-5. Local-with-networking sandbox, selected authorized, and prohibited operations Operation Non-.swf resources in the local realm local-with- filesystem .swf files local-with- networking .swf files local-trusted .swf files Remote-realm resources Loading content Allowed Prohibited Allowed Allowed Allowed Accessing con- tent as data Prohibited n/a n/a n/a Allowed by dis- tributor permis- sion only Cross-scripting n/a Prohibited Allowed Allowed by creator permis- sion only Allowed by creator permission only Loading data Prohibited n/a n/a n/a Allowed by distributor permission only Table 19-6. Local-trusted sandbox, selected authorized, and prohibited operations Operation Non-.swf resources in the local realm local-with- filesystem .swf files local-with- networking .swf files local-trusted .swf files Remote-realm resources Content loading Allowed Allowed Allowed Allowed Allowed Accessing content as data Allowed n/a n/a n/a Allowed Cross-scripting n/a Allowed Allowed Allowed Allowed Data loading Allowed n/a n/a n/a Allowed 422 | Chapter 19: Flash Player Security Restrictions Socket Security In ActionScript, socket connections are made with the XMLSocket, Socket, and NetConnection classes. Table 19-7 and Table 19-8 list the specific locations and ports to which the XMLSocket and Socket methods can open socket connections. The tables do not cover the NetConnection class, which is used with Adobe Flash Media Server and Adobe Flex. For information on NetConnection security, see the docu- mentation for those products. In both Table 19-7 and Table 19-8, distributor permission means the socket-server operator has made the appropriate cross-domain policy file available; see the sec- tion“Distributor Permissions (Policy Files),” later in this chapter. Table 19-7 describes whether a remote .swf file can make a socket connection to the four locations listed. Table 19-8 describes whether a .swf file with the security-sandbox-type in the left- most column can make a socket connection to the locations listed in the remaining columns. Example Security Scenarios To give the information we’ve studied so far a practical context, let’s look at a few examples where Flash Player’s security system prevents data from being retrieved by an unauthorized party. Each scenario presents the technique a hacker would use to access data if there were no Flash Player security and then describes how Flash Player’s security system prevents the hacker from accessing the target data. Table 19-7. Remote sandbox authorized and prohibited socket connections Local realm, any port Remote realm, within .swf’s region of origin, port 1024 and higher Remote realm, within .swf’s region of origin, port 1023 and lower Remote realm, outside .swf ’s region of origin, any port Allowed by distributor permission only Allowed Allowed by distributor permission only Allowed by distributor permission only Table 19-8. Local sandboxes authorized and prohibited socket connections Security-sandbox-type Local realm, any port Remote realm, any port Local-with-filesystem Prohibited Prohibited Local-with-networking Allowed by distributor permission only Allowed by distributor permission only Local-trusted Allowed Allowed Example Security Scenarios | 423 Snoopy Email Attachment—Without Flash Player Security Joe Hacker wants to perform an identity theft on Dave User. Joe knows that Dave reports his taxes using ABCTax software on Microsoft Windows. Joe does a little research, and finds that ABCTaxkeeps each year’s taxreturn information in an XML file stored in the following location: c:\ABCTax\taxreturn.xml. If Joe can get that file, he can use the information it contains to open a bank account and apply for credit cards in Dave’s name. So Joe sends Dave an email with a harmless looking anima- tion, cartoon.swf, as an attachment. Dave opens the email and watches the cartoon in a web browser on his local machine. Without Dave’s knowledge, cartoon.swf secretly uses URLLoader.load( ) to retrieve taxreturn.xml from the local filesystem. Then, cartoon.swf uses flash.net.sendToURL( ) to upload taxreturn.xml to Joe’s web site. Joe gets a credit card in Dave’s name and buys a Nintendo Wii with lots of great games. Snoopy Email Attachment—With Flash Player Security As before, Joe sends Dave an email with a harmless looking animation, cartoon.swf,as an attachment. Dave opens the email and watches the cartoon in a web browser on his local machine. Because cartoon.swf is opened from the local realm, Flash Player checks whether the cartoon.swf file was compiled with network support. Let’s first suppose that cartoon.swf was compiled without network support. In that case, Flash Player assigns cartoon.swf the local-with-filesystem security-sandbox-type. As before, cartoon. swf secretly uses URLLoader.load( ) to retrieve taxreturn.xml from the local filesystem. According to Table 19-4, cartoon.swf is allowed to load that local data. Then, cartoon. swf attempts to use flash.net.sendToURL( ) to upload taxreturn.xml to Joe’s web site, but the attempt is blocked because local-with-filesystem .swf files are not allowed to perform flash.net.sendToURL( ) operations. (Our earlier tables didn’t specifically cover the security restrictions for flash.net.sendToURL( ), but as mentioned earlier, you can determine the security restrictions that apply to any method in the Flash Player API by consulting Adobe’s ActionScript Language Reference.) Now let’s suppose that cartoon.swf was compiled with network support. In that case, Flash Player assigns cartoon.swf the local-with-networking security-sandbox-type. As before, cartoon.swf attempts to secretly use URLLoader.load( ) to retrieve taxreturn.xml from the local filesystem. But the attempt is blocked because, per Table 19-5, local- with-networking .swf files cannot use data-load operations with resources from the local realm. Joe has to buy his own Nintendo Wii. 424 | Chapter 19: Flash Player Security Restrictions Internal Corporate Information—Without Flash Player Security Joe Hacker wants some insider information for a stock deal. Joe used to work at WYZ Corporation. WYZ Corporation’s public web site is www.wyzcorp.com. Joe left WYZ on good terms, so WYZ has hired him on contract to update the company pro- file, profile.swf,onwww.wyzcorp.com. Joe knows that WYZ is planning to release an important product that will affect the company’s stock price. Joe also knows that WYZ Corporation keeps its future product release dates on an internal web site that is behind the company firewall, at the following location: strategy.wyzcorp.com/ releasedates.html. If Joe can secretly obtain the new product’s release date, he can buy WYZ stock the day before the product ships and sell it at a profit later. So Joe adds some code to profile.swf that uses URLLoader.load( ) to attempt to load the file: strategy.wyzcorp.com/releasedates.html. An employee of WYZ then views the company’s profile at, www.wyzcorp.com/profile.swf. Because the employee’s com- puter is behind the firewall, it has access to strategy.wyzcorp.com, so the attempt to load releasedates.html succeeds! Without the employee’s knowledge, profile.swf uses flash.net.sendToURL( ) to upload releasedates.html to Joe’s web site. WYZ’s stock takes off, and Joe retires to a life of painting and urban exploration. Internal Corporate Information—With Flash Player Security As before, Joe posts profile.swf to www.wyzcorp.com, and profile.swf uses URLLoader. load( ) to attempt to load the file: strategy.wyzcorp.com/releasedates.html.An employee of WYZ then views the company’s profile at, www.wyzcorp.com/profile.swf. Because profile.swf is opened from the remote realm, ActionScript assigns it the remote security-sandbox-type. When www.wyzcorp.com/profile.swf attempts to load strategy.wyzcorp.com/releasedates.html, the attempt is blocked because, per Table 19-3, a remote .swf file cannot use data-load operations with resources outside its remote region of origin. Joe hopes to get a gig making banner ads for WYZ’s new product so he can pay next month’s rent. Cross-Web Site Information—Without Flash Player Security Joe Hacker wants to steal some bank account information. Joe works at Hipster Ad Agency, which produces advertising for ReallyHuge Bank. ReallyHuge Bank has an online banking application posted at www.reallyhugebank.com/bank.swf. The bank.swf application loads advertising from www.hipsteradagency.com/ad.swf. Joe has looked at bank.swf with a .swf decompiler, and knows the variables in which bank.swf stores its users’ bank account numbers and passwords. Maliciously, Joe adds code to ad.swf that reads those variables from its parent, bank.swf and then uses flash.net.sendToURL( ) to Choosing a Local Security-Sandbox-Type | 425 send the stolen information to Joe’s web site. Whenever a bank.swf user logs into an account, Joe receives the account number and password. Joe donates a mysteriously large amount of money to Greenpeace. Cross-Web Site Information—With Flash Player Security As before, Joe adds code to ad.swf that reads those variables from its parent, bank.swf. A user launches www.reallyhugebank.com/bank.swf, and bank.swf loads Joe’s ill-inten- tioned www.hipsteradagency.com/ad.swf. Because ad.swf is opened from the remote realm, ActionScript assigns it the remote security-sandbox-type. When ad.swf attempts to read bank.swf ’s variables, the attempt is blocked because, per Table 19-3, a remote .swf file cannot use data-load operations with resources outside its remote region of origin. Joe donates a modest amount of money to Greenpeace. Choosing a Local Security-Sandbox-Type We now have a good understanding of the safeguards provided by each security- sandbox-type. We’ve also seen that .swf files opened or loaded from remote realm locations are always assigned the remote security-sandbox-type, and that .swf files opened or loaded from local realm locations are assigned one of three local security- sandbox-types. Now let’s take a closer look at the mechanisms involved in choosing between those three local security-sandbox-types. Each of the following three sec- tions presents a scenario in which a developer uses one of the three local security- sandbox-types. Each scenario describes both the rationale for, and mechanism for choosing, each security-sandbox-type. Compiling a Local-with-Filesystem .swf File Susan is developing a calendar application, calendar.swf, to be posted on a hotel’s web site. The calendar loads holiday information from an external XML file, holiday.xml. The application is near complete, so Susan needs to send it to her client for review. Even though calendar.swf will eventually be posted on a web site, the client wants to demonstrate it to a variety of people at the hotel. The client won’t always have an Inter- net connection available during demonstrations. Hence, Susan sends both calendar.swf file and holiday.xml to the client. In order to allow calendar.swf to load holiday.xml from the local filesystem during the client’s demonstrations, Susan compiles calendar .swf with the -use-network compiler flag set to false. Depending on the authoring tool Susan is using, she uses different mechanisms to set the -use-network compiler flag. In FlexBuilder 2, Susan follows these steps to set the -use-network compiler flag to false: 426 | Chapter 19: Flash Player Security Restrictions 1. In the Navigator panel, Susan selects the project folder for the calendar application. 2. On the Project Menu, she chooses Properties. 3. On the Properties dialog, she chooses ActionScript Compiler. 4. Under Additional compiler arguments, she enters: -use-network=false. 5. To confirm the setting, Susan clicks OK. In Flash authoring tool, Susan follows these steps to set the -use-network compiler flag to false: 1. On the File menu, Susan chooses Publish Settings. 2. On the Publish Settings dialog, she chooses the Flash tab. 3. In the Local playback security pulldown-menu she chooses Access local files only. 4. To confirm the setting, Susan clicks OK. When using the FlexSDK command-line compiler, Susan specifies the value of the -use-network flag as an argument to mxmlc. Here is the command Susan issues when she’s working on Microsoft Windows: mxmlc.exe -use-network=false -file-specs c:\projects\calendar\Calendar.as -output c:\projects\calendar\bin\Calendar.swf Compiling a Local-with-Networking .swf File Dorian is making a video game, race.swf, for her company’s web site. Visitors to the web site can play the game online and submit their high scores. Dorian wants to make a downloadable version of the game available for people to play when they are not connected to the Internet. When the user is not connected, the downloadable game will retain the user’s high score in a local shared object and submit the score to the high-score server the next time the user connects to the Internet. Dorian knows that the general public prefers not to run executable files from unknown sources, so she chooses to make her game available as a downloadable .swf file rather than an executable projector. To allow the game to connect to the high-score server when running as a local .swf file, Dorian compiles race.swf with the -use-network compiler flag set to true. To set the -use-network flag, Dorian uses the exact same techniques that Susan used in the preceding calendar.swf scenario but specifies the value true instead of false when setting -use-network. When using Flash, for “Local playback security,” Dorian chooses “Access network only” instead of “Access local files only.” Choosing a Local Security-Sandbox-Type | 427 Granting Local Trust Colin is making an administrator tool, admin.swf, for a socket server application. The admin.swf file is intended to run either on the same domain as the socket server or on the server administrator’s local filesystem. The administrator tool connects to a remote socket server and does not load any local files, so Colin compiles it as a local- with-networking .swf file. The administrator tool’s first screen presents a simple server name and password login form. When the user logs in, the administrator tool offers to remember the server’s password. If the user accepts the offer, the administrator tool stores the pass- word in a local shared object. Next time the user logs in, the administrator tool auto- matically populates the login form’s password text field. During development, Colin suddenly realizes that because admin.swf is a local-with- networking .swf file, other local-with-networking .swf files on the same computer as admin.swf will be permitted to load admin.swf and read the password out of the text field! To prevent this possible breach of security, Colin wisely decides that admin.swf must be assigned the local-trusted security-sandbox-type. If admin.swf ’s security-sandbox- type is local-trusted, then other local-with-networking .swf files will not be able to read the password text field. To make admin.swf a local trusted .swf file, Colin writes an installer that designates admin.swf ’s location as a trusted location by placing a configuration file in the local machine’s Global Flash Player Trust directory. Following ActionScript’s official for- mat for local-trust configuration files, the configuration file contains only a single line of text: the local filesystem location of admin.swf. As a result, when Flash Player loads admin.swf from that specified location, it sets admin.swf ’s security-sandbox- type to local-trusted. For complete coverage of creating and managing local-trust configura- tion files, see Adobe’s documentation, under Programming Action- Script 3.0 ➝ Flash Player APIs ➝ Flash Player Security ➝ Overview of permission controls ➝ Administrative user controls. Colin also recognizes that users of the administration tool might want to move admin.swf to arbitrary new locations. To allow the user to move admin.swf to a new location without losing its local-trusted status, Colin includes the instructions in the administration tool’s documentation (see sidebar). The socket-server-administration-tool scenario just presented demonstrates the two available mechanisms for classifying a local .swf file as trusted: configuration files on the computer running Flash Player (which Colin’s installer provided) and the Flash Player Settings Manager (accessed by the user). For complete coverage of the Flash 428 | Chapter 19: Flash Player Security Restrictions Player Settings Manager, see Adobe’s documentation, under Programming Action- Script 3.0 ➝ Flash Player APIs ➝ Flash Player Security ➝ Overview of permission con- trols ➝ User controls. Note that if Flash Player is running when trust is granted (via either configuration files or the Flash Player Settings Manager), the new trust status for the affected .swf file(s) does not come into effect until Flash Player is restarted. For the plug-in and ActiveX control versions of Flash Player “restarting” means shutting down all instances of Flash Player—even those in other browser windows! Developers Automatically Trusted To simplify the testing of local content that is intended for web deployment, Adobe’s FlexBuilder 2 automatically grants trust to projects under development. To grant trust, it adds a path entry for each project’s output folder (typically, /bin/) to the flexbuilder.cfg file in the User Flash Player Trust directory. Likewise, the Flash authoring tool’s Test Movie-mode Player automatically trusts all local-realm .swf files it opens or loads. Consequently, when loading assets during development, you might not encounter security violations that would affect your enduser. For example, a .swf file in a project’s /bin/ folder would be allowed to load a local file, even if it has network-only permissions. To test your application as your end user will see it, be sure to run it in its target envi- ronment. For example, for web-based applications, be sure to test over the Web. For nontrusted local applications created in FlexBuilder 2, test from a local nontrusted directory (the system desktop is typically a nontrusted directory). For nontrusted local applications created in the Flash authoring tool, use File ➝ Publish Preview ➝ HTML to preview in a browser (the Flash authoring tool does not automatically trust content previewed in a browser). Moving admin.swf to a Custom Location Before moving admin.swf to a new location, be sure to register it as a local-trusted .swf file by following these steps: 1. Open the online Flash Player Settings Manager by browsing to the following web page: http://www.macromedia.com/support/documentation/en/flashplayer/help/ settings_manager04.html 2. Under Global Security Settings ➝ Always trust files in these locations, click Edit locations ➝ Add location. 3. Enter or browse to the location you wish to trust. Distributor Permissions (Policy Files) | 429 During testing you should always explicitly verify that your applica- tion’s security-sandbox-type matches the security-sandbox-type you intend to use during deployment. To check a .swf file’s security-sandbox-type at runtime, retrieve the value of the flash.system.Security.sandboxType variable from within that .swf file. To manually verify which directories are trusted on a given computer, consult the trust files in the User Flash Player Trust directory and the Global Flash Player Trust directory, and the online Flash Player Settings Manager at: http://www.adobe.com/ support/documentation/en/flashplayer/help/index.html. To remove trust for a FlexBuilder 2 project (thus simulating the end-user experience for nontrusted applications), delete the appropriate path entry in the flexbuilder.cfg file in the User Flash Player Trust directory. Note, however, that because Flex Builder 2 automatically restores the flexbuilder.cfg file when it creates a new project, you will have to delete the path entry every time you create or import a project. For the location of the User and Global trust directories, see Adobe’s documentation, under Programming ActionScript 3.0 ➝ Flash Player APIs ➝ Flash Player Security ➝ Overview of permission controls. Default Local Security-Sandbox-Type Both FlexBuilder 2 and the command line compiler mxmlc set the -use-network com- piler flag to true when it is not specified. Hence, by default, when a .swf file compiled with either FlexBuilder 2 or mxmlc runs in the local realm in any nontrusted location, it will be assigned the local-with-networking security-sandbox-type. By perhaps surprising contrast, the Flash authoring tool’s default value for the “Local playback security” publishing option is “Access local files only.” Therefore, by default, when a .swf file compiled with the Flash authoring tool runs in the local realm in any nontrusted location, it will be assigned the local-with-filesystem secu- rity-sandbox-type. To avoid confusion, always explicitly specify your desired value for the -use-network compiler flag and the Flash authoring tool’s “Local playback security” publishing option. Distributor Permissions (Policy Files) Throughout this chapter, we’ve seen plenty of ways in which Flash Player’s security system restricts a .swf file’s access to foreign resources. Now let’s examine how, in some cases, a resource distributor can use distributor permissions to override those restrictions. 430 | Chapter 19: Flash Player Security Restrictions Recall that a “resource distributor” is the party that delivers a resource from a given remote region. For example, a web site administrator and a socket-server administrator are both resource distributors. As the party responsible for a given remote region’s resources, a resource distributor can grant .swf files from foreign origins access to those resources. To grant .swf files access to a given set of resources, a resource distributor uses a special permission mechanism known as a policy file. A policy file is a simple XML document that con- tains a list of trusted .swf file origins. In general terms, a policy file gives .swf files from its list of trusted origins access to resources that would otherwise be inaccessi- ble due to Flash Player’s security restrictions. The types of operations a policy file can potentially authorize are: • Accessing content as data • Loading data • Connecting to a socket • Import loading (discussed separately in the later section “Import Loading”) A policy file cannot authorize cross-scripting operations. For informa- tion on authorizing cross-scripting, see the section “Creator Permis- sions (allowDomain( )).” Typically, policy files are used to enable interoperation between different remote regions. For example, a policy file might give http://site-a.com/map.swf permission to read the pixels of http://site-b.com/satellite-image.jpg or permission to load http://site- b.com/map-data.xml. Per Table 19-3, Table 19-5, Table 19-7, and Table 19-8, a policy file can give a .swf file access to otherwise inaccessible resources in the following situations: • When a remote .swf file attempts to perform an accessing-content-as-data opera- tion on a remote-realm resource outside its region of origin • When a remote .swf file attempts to perform a loading-data operation on a remote-realm resource outside its region of origin • When a local-with-networking .swf file attempts to perform an accessing-con- tent-as-data operation on a remote-realm resource • When a local-with-networking .swf file attempts to perform a loading-data opera- tion on a remote-realm resource • When a remote .swf file attempts to connect to a socket within its region of origin, but below port 1024 • When a remote .swf file attempts to connect to a socket outside its region of origin Distributor Permissions (Policy Files) | 431 • When a local-with-networking .swf file attempts to connect to a socket in the remote realm The upcoming sections explore how a resource distributor can use a policy file to authorize access to resources in each of the preceding situations. For additional policy-file coverage, see Adobe’s documentation, under Programming ActionScript 3.0 ➝ Flash Player APIs ➝ Flash Player Security ➝ Overview of permis- sion controls ➝ Web Site controls (cross-domain policy files). In Flash Player 6, policy files were used to allow cross-domain com- munication only, and were, therefore, called cross-domain policy files. Since Flash Player 7.0.19.0, policy files have also been used to allow socket connections to low-range ports. To reflect this broader pur- pose, this book uses the shorter term policy file, but you should expect to see the original term, cross-domain policy file, in other documentation. Authorizing Loading-Data and Accessing-Content-as-Data Operations To grant .swf files from a given set of origins authorization to perform loading-data or accessing-content-as-data operations on a given set of remote resources, follow these general steps: 1. Create a policy file. 2. Post the policy file within the same remote region (i.e., domain or IP address) as the resource to which authorization is being granted. The next two sections cover the preceding steps in detail. Once we’ve studied how to create and post policy files, we’ll examine the process by which a .swf file obtains permission from a policy file to perform loading-data and accessing-content-as-data operations. Creating the policy file To create a policy file, follow these steps: 1. Create a new text file. 2. Add a list of the desired authorized origins to the policy file, using Adobe’s offi- cial policy-file syntax. 3. Save the text file. Adobe’s official policy-file syntax is XML-based and has the following structure: 432 | Chapter 19: Flash Player Security Restrictions where domainOrIP specifies the domain name or IP address of an authorized origin. A .swf file loaded from an authorized origin is permitted to perform loading-data and accessing-content-as-data operations on a given set of resources. As we’ll learn in the next section, the specific set of resources to which a policy file grants access is deter- mined by the location from which the policy file is served. Any number of tags can be included in a policy file. For exam- ple, the following policy file defines three authorized origins: example1.com, example2.com, and example3.com. Within the value of the domain attribute, the * character indicates a wildcard. For example, the following policy file authorizes example1.com and any subdomain of example1.com, no matter how deeply nested (e.g., games.example1.com, driving. games.example1.com, and so on): When used on its own, the * character authorizes all origins: To include the local realm as an authorized origin, a policy file must explicitly trust all origins by specifying * (any origin) for the domain attribute. Hence, a web site wishing to make XML files loadable by local-with-networking .swf files must specify * for the domain attribute. Posting the policy file Once a policy file has been created, it must be posted within the same remote region (i.e., domain or IP address) as the resource to which access is being granted. For Distributor Permissions (Policy Files) | 433 example, if the policy file grants access to content at www.example.com, then the policy file must also be posted at www.example.com. The set of resources to which a policy file grants access is determined by the specific location at which it is posted. When a policy file is posted in the root directory of a web site, it grants access to the entire web site. For example, a policy file posted at http://www.example.com grants access to all content at www.example.com. When a policy file is posted in a subdirectory of a web site, it grants access to that directory and its child subdirectories only. For example, a policy file posted at http: //www.example.com/assets grants access to all content in the /assets/ directory and its subdirectories, but does not grant access to content in the root directory of www.example.com, nor to any other subdirectory on www.example.com. To help automate the loading of policy files, ActionScript defines a default name and location for policy files. Any policy file that is named crossdomain.xml and is placed in the root directory of a web site is said to reside in the default policy file location, and is known as the web site’s default policy file. As we’ll learn in the next two sec- tions, placing a policy file in the default policy file location reduces the amount of code required to obtain that policy file’s permissions. Obtaining a policy file’s permission to load data When a web site has a default policy file authorizing a given remote region, .swf files from that remote region can load data from that web site by simply performing the desired loading-data operation. For example, suppose site-a.com has the following default policy file, which authorizes site-b.com and www.site-b.com: To load http://site-a.com/assets/file.xml, any .swf file from www.site-b.com or site-b.com would use the following code: var urlloader:URLLoader = new URLLoader( ); urlloader.load(new URLRequest("http://site-a.com/assets/file.xml")); Because site-a.com’s policy file is in the default location, Flash Player finds it auto- matically and allows file.xml to load. On the other hand, if a web site’s policy file is posted in a nondefault location, .swf files from authorized remote regions must manually load that policy file before attempting to load data from that web site. To manually load a policy file, we use the Security class’s static method loadPolicyFile( ), which has the following general form: Security.loadPolicyFile("http://domainOrIP/pathToPolicyFile"); 434 | Chapter 19: Flash Player Security Restrictions In the preceding generalized code, domainOrIP is the domain or IP address at which the policy file is posted, and pathToPolicyFile is the location of the policy file on that server. Note that, as mentioned earlier, Flash Player considers numerically specified IP addresses distinct from their equivalent domain names. For example, suppose site-c.com posts the following policy file at http://site-c.com/ assets/policy.xml; the policy file authorizes site-d.com and www.site-d.com. To load http://site-c.com/assets/file.xml, any .swf file from www.site-d.com or site-d.com would use the following code: // Load policy file first Security.loadPolicyFile("http://site-c.com/assets/policy.xml"); // Then perform the load operation var urlloader:URLLoader = new URLLoader( ); urlloader.load(new URLRequest("http://site-c.com/assets/file.xml")); Notice that the preceding code issues the loading-data command immediately after issuing the policy-file-loading command. Flash Player automatically waits for the policy file to load before proceeding with the loading-data operation. Once a policy file has been loaded via Security.loadPolicyFile( ), its authorization remains in effect for all future loading-data operations issued by the .swf file. For example, the following code manually loads a policy file, and then performs two load operations that both rely on that policy file’s authorization: // Load policy file once Security.loadPolicyFile("http://site-c.com/assets/policy.xml"); // Perform two authorized load operations var urlloader1:URLLoader = new URLLoader( ); urlloader1.load(new URLRequest("http://site-c.com/assets/file1.xml")); var urlloader2:URLLoader = new URLLoader( ); urlloader2.load(new URLRequest("http://site-c.com/assets/file2.xml")); Let’s consider a practical example showing how a policy file posted in a web site’s subdirectory might be used in a real-world situation. Suppose Graham runs a free stock-information web site, stock-feeds-galore.com. Graham stores his latest stock feed in an XML file, in the following location: stock-feeds-galore.com/latest/feed.xml Graham wants to make the contents of the /latest/ directory publicly accessible to all Flash files from any origin but does not want to make the entire web site accessible. Distributor Permissions (Policy Files) | 435 Hence, Graham posts the following policy file, named policy.xml, in the /latest/ direc- tory (notice the use of the domain wildcard, *): Graham then posts a notice on stock-feeds-galore.com telling ActionScript develop- ers that the location of the policy file is: stock-feeds-galore.com/latest/policy.xml Meanwhile, James is creating a stock-ticker application, stockticker.swf, which he intends to post at his web site, www.some-news-site.com. James’ application loads Graham’s stock feed. Because www.stock-feeds-galore.com’s policy file is not in the default location, James must load the policy file before loading the stock feed. Here’s the code James uses to load Graham’s policy file: Security.loadPolicyFile("http://stock-feeds-galore.com/latest/policy.xml") After issuing the request to load the policy file, James uses a URLLoader object to load the feed.xml file, as follows: var urlLoader:URLLoader = new URLLoader( ); urlLoader.load(new URLRequest( "http://stock-feeds-galore.com/latest/feed.xml")); In response, Flash Player loads http://stock-feeds-galore.com/latest/policy.xml, finds the required authorization within that policy file, and then proceeds with the load- ing of feed.xml. Now that we’ve seen how to obtain a policy file’s permission to load data, let’s explore how to obtain a policy file’s permission to perform an accessing-content-as- data operation. Obtaining a policy file’s permission to access content as data The code we use to obtain a policy file’s permission to access content as data varies according to the type of data being accessed. To obtain a policy file’s permission to access an image as data, follow these steps: 1. If the policy file is not in the default location, load it with Security.loadPolicyFile( ) (as discussed in the previous section). 2. Create a LoaderContext object, and set its checkPolicyFile variable to true. 3. Load the desired image with Loader.load( ); for Loader.load( )’s context parame- ter, pass the LoaderContext object from Step 2. 4. Once the image has loaded, perform the accessing-content-as-data operation. For example, suppose site-a.com posts the following policy file at http://site-a.com/ assets/policy.xml; the policy file authorizes site-b.com and www.site-b.com. 436 | Chapter 19: Flash Player Security Restrictions To access http://site-a.com/assets/image.jpg as data, any .swf file from www.site-b.com or site-b.com would use the following code: // Step 1: The policy file is not in the default location, // so load it manually. Security.loadPolicyFile("http://site-a.com/assets/policy.xml"); // Step 2: Create a LoaderContext object and set // its checkPolicyFile variable to true. var loaderContext = new LoaderContext( ); loaderContext.checkPolicyFile = true; // Step 3: Load the image. Pass the LoaderContext object to Loader.load( ). theLoader.load(new URLRequest("http://site-a.com/assets/image.jpg"), loaderContext); // Step 4: Later, once the application has verified that the image // has finished loading, access the image as data trace(theLoader.content); To obtain a policy file’s permission to access a foreign sound as data, follow these steps: 1. If the policy file is not in the default location, load it with Security.loadPolicyFile( ) (as discussed in the preceding section). 2. Create a SoundLoaderContext object, and set its checkPolicyFile variable to true. 3. Load the desired sound with the Sound class’s instance method load( ). For load( )’s context parameter, pass the SoundLoaderContext object from Step 2. 4. Once the sound has sufficiently loaded (as determined by using the Sound class’s load-progress events), perform the authorized accessing-content-as-data operation. For example, suppose site-c.com has the following default policy file, which autho- rizes site-d.com and www.site-d.com: To access http://site-c.com/sounds/song.mp3 as data, any .swf file from www.site-d. com or site-d.com would use the following code: Distributor Permissions (Policy Files) | 437 // Step 1: The policy file is in the default location, so no need to // manually load it // Step 2: Create a SoundLoaderContext object and set // its checkPolicyFile variable to true. var soundLoaderContext = new SoundLoaderContext( ); soundLoaderContext.checkPolicyFile = true; // Step 3: Load the sound. Pass the SoundLoaderContext object // to Loader.load( ). theSound.load(new URLRequest("http://example.com/sounds/song.mp3"), // Step 4: Later, once the application has verified that the sound's // ID3 data loaded (as indicated by the Event.ID3 event), access the // sound as data trace(theSound.id3); Note that setting either the LoaderContext or the SoundLoaderContext class’s instance variable checkPolicyFile to true does not determine whether an asset is loaded. When either Loader’s load( ) or SoundLoader’s load( ) method runs, the asset is always loaded, even when no policy file authorizes the requesting .swf file’s region of origin; however, if code in that .swf file later attempts to access the loaded asset as data, Flash Player will throw a SecurityError exception. Let’s look at a real-world example showing how a web site’s default policy file might be used to authorize an accessing-content-as-data operation. Remember Graham’s stock-feeds-galore.com web site? It’s doing so well that Graham finds himself with some time on his hands. He decides to experiment with Action- Script bitmap programming and creates a facial-recognition application that can automatically add a funny party hat to any photo of a person’s face. Graham’s pretty pleased with himself. Graham’s friend Andy runs a lottery corporation with a promotional web site, www.lotterylotterylottery.com. Andy sees Graham’s party-hat application and decides it would make a good marketing campaign. In the campaign, lottery win- ners post their photos to photos.lotterylotterylottery.com. The main site, www. lotterylotterylottery.com, then picks a random photo for the home page, showing a lottery winner wearing a party hat. Andy hires Graham to produce the code for the campaign. Graham puts his facial-recognition application, partyhat.swf,onwww.lotterylotterylottery. com. He then writes a Perl script, randompic.pl, that returns a random photo (.jpg file) from photos.lotterylotterylottery.com. He places randompic.pl in photos.lotterylotterylottery.com/ cgi-bin. The partyhat.swf file from www.lotterylotterylottery.com needs access to the pixels of loaded photos from photos.lotterylotterylottery.com. To authorize that access, 438 | Chapter 19: Flash Player Security Restrictions Graham places the following policy file in the root of photos.lotterylotterylottery.com and names it crossdomain.xml: Notice that Graham is careful to include both www.lotterylotterylottery.com and lotterylotterylottery.com in the policy file. That way, partyhat.swf will function prop- erly when loaded from either of those URLs. Graham is also careful to exclude the domain “*” because his policy applies to specific domains only, not to the entire world. To load a photo, Graham uses the following code. (Notice that Security.loadPolicyFile() is not needed because Graham posted the policy file in the default policy file location.) var loaderContext = new LoaderContext( ); loaderContext.checkPolicyFile = true; loader.load( new URLRequest("http://photos.lotterylotterylottery.com/randompic.pl"), loaderContext); In response, Flash Player loads http://photos.lotterylotterylottery.com/crossdomain.xml, finds the required authorization within that policy file, loads the photo returned by randompic.pl, and then allows partyhat.swf to access to the pixels of the loaded photo. Once the photo is loaded, partyhat.swf safely accesses the loaded photo. For example, here’s the code Graham uses to run the partyhat.swf method that adds the party hat to the loaded photo (notice that the loaded image’s Bitmap object, loader.content, is referenced by permission): addHat(loader.content); Now that we’ve seen how to use a policy file to authorize loading-data and accessing- content-as-data operations, let’s explore how to use a policy file to authorize socket connections. Using a Policy File to Authorize Socket Connections To authorize socket connections with a policy file follow these general steps: 1. Create the policy file. 2. Serve the policy file via a socket server or an HTTP server running on the same domain or IP as the desired socket connection. The next three sections cover the preceding steps in detail. Distributor Permissions (Policy Files) | 439 Create the policy file Policy files that grant permission to perform socket connections have the same basic syntaxas policy files that grant permission to perform loading-data and accessing- content-as-data operations. However, in policy files that grant permission to per- form socket connections, the tag includes an additional attribute, to-ports, as shown in the following code: The to-ports attribute specifies the ports to which a .swf file from domainOrIP is authorized to connect. The ports can be listed individually (separated by commas), or in ranges (separated by the - character). For example, the following policy file grants the following permissions: • .swf files from example1.com can connect to ports 9100 and 9200. • .swf files from example2.com can connect to ports 10000 through 11000. Within the value of to-ports, the * character acts as a wildcard; when a policy file is retrieved over a socket on a port less than 1024, * indicates that access to any port is authorized; when a policy file is retrieved over a socket on a port greater than or equal to 1024, * indicates that access to any port greater than or equal to 1024 is authorized. Because ports under 1024 are considered sensitive, a policy file served over port 1024 or greater can never authorize access to ports below 1024, even if those ports are listed specifically. For example, if the following policy file is served on port 2000, it grants .swf files from example3.com permission to connect to all ports greater than or equal to 1024. 440 | Chapter 19: Flash Player Security Restrictions But when the very same policy file is served on port 1021 (which is less than 1024), it grants .swf files from example3.com permission to connect to any port. Therefore, to grant .swf files from any location permission to connect to any port, we would serve the following policy file on a port below 1024: When a policy file is retrieved over a socket, to-ports is mandatory; if it is not speci- fied, access is not granted to any port. Now that we know how to create a policy file that authorizes a socket connection, let’s examine how a .swf file can obtain that policy file’s authorization. Socket-based policy-file retrieval Policy files that authorize socket connections can be served either directly over a socket or via HTTP. Policy files served over a socket must be served on the same domain or IP as the desired socket connection, either on the same port as the desired socket connection, or on a different port. In either case, the server running on the port over which the policy file is served must communicate with Flash Player using a very simple policy-file-retrieval protocol. The protocol consists of a single tag, , which Flash Player sends over the socket when it wishes to load a policy file authorizing a socket connection. In response, the socket server is expected to send Flash Player the text of the policy file in ASCII format, plus a zero byte (i.e., the ASCII null character), and then close the connection. Hence, custom servers that wish to handle both policy file requests and normal communications over the same port must implement code to respond to policy-file requests as well as code to manage normal socket communications. When a server handles policy file requests and normal communications over the same port, .swf files from authorized regions can connect to that server by performing the desired socket connection operation. For example, suppose a multiuser game server run- ning at site-a.com is designed to handle both game communication and policy file requests over port 3000. The game server’s policy file authorizes www.site-b.com and site-b.com, as follows: Distributor Permissions (Policy Files) | 441 To connect to port 3000 at site-a.com, any .swf file loaded from www.site-b.com or site-b.com would use the following code: var socket:Socket = new Socket( ); try { socket.connect("site-a.com", 3000); } catch (e:SecurityError) { trace("Connection problem!"); trace(e.message); } When the preceding code runs, before the requested connection to port 3000 is allowed, Flash Player automatically makes a separate connection to port 3000 and sends a message to the game server. The game server responds with site-a.com’s policy file and then closes the connection. That policy file contains the connecting .swf file’s origin as an authorized region, so the original socket connection is then allowed to proceed. In all, two separate connections are made: one for the policy file, and, subsequently, one for the original socket- connection request. In some situations, it might not be practical or possible for a server to respond to a Flash Player policy-file request. For example, a .swf file might wish to connect to an existing SMTP mail server that does not understand the meaning of the instruction . To authorize the connection, the mail server administrator must make a policy file available via a different port at the same domain or IP address as the mail server. The server at that different port can be an extremely sim- ple socket server that merely listens for connections, receives instructions, returns a policy file in response, and then closes the connection. When a policy file is served on a different port than the desired socket connection (as is the case in our mail server example), .swf files from authorized regions must load that policy file manually before requesting the desired socket connection. To load a policy file manually from an arbitrary port, we use the following general code: Security.loadPolicyFile("xmlsocket://domainOrIP:portNumber"); where domainOrIP is the domain or IP address of the server, and portNumber is the port number over which to retrieve the policy file. Once again, Flash Player consid- ers numerically specified IP addresses distinct from their equivalent domain names. In the preceding code, notice the mandatory use of the special xmlsocket:// proto- col. The protocol name, “xmlsocket,” describes the type of connection used to retrieve the policy file, not the type of connection the policy file authorizes. A policy file loaded using the xmlsocket:// protocol authorizes con- nections made via both Socket and XMLSocket, not just XMLSocket. 442 | Chapter 19: Flash Player Security Restrictions Once a manual request to load a policy file has been issued, a follow-up request to con- nect to the desired port can immediately be issued. For example, suppose site-c.com runs a simple policy file server on port 1021, and that site-c’s policy file authorizes site-d.com and www.site-d.com to connect to port 25. Here’s the policy file: To connect to port 25 at site-c.com, any .swf file loaded from site-d.com or www. site-d.com would use the following code. Notice that the .swf file requests the socket connection to port 25 immediately after issuing the request to load the pol- icy file over port 1021. Flash Player patiently waits for the policy file to load before proceeding with the connection to port 25. // Load the policy file manually Security.loadPolicyFile("xmlsocket://site-c.com:1021"); var socket:Socket = new Socket( ); try { // Attempt the connection (immediately after policy file has // been requested) socket.connect("site-c.com", 25); } catch (e:SecurityError) { trace("Connection problem!"); trace(e.message); } When the preceding code runs, before allowing the requested connection to port 25, Flash Player makes a separate connection to port 1021 and sends a message to the server listening on that port. The server on port 1021 responds with site-c.com’s policy file and then closes the connection. That policy file contains the connecting .swf file’s origin as an authorized region, so the connection to port 25 is then allowed to proceed. Now let’s take a look at an alternative way to authorize a socket-connection: HTTP- based policy files. HTTP-based policy-file retrieval Prior to Flash Player 7.0.19.0, Flash Player required policy files authorizing socket connections to be served over HTTP. Primarily for backwards compatibility, Action- Script 3.0 continues to support the authorization of socket connections by policy files served over HTTP. However, in order to authorize a socket connection, a policy file served via HTTP must meet the following requirements: • It must be named crossdomain.xml. • It must reside in the web server’s root directory. Distributor Permissions (Policy Files) | 443 • It must be served over port 80 at the domain or IP address of the desired socket connection. • In ActionScript 3.0, it must be manually loaded via Security.loadPolicyFile( ). Furthermore, policy files served via HTTP do not use the to-ports attribute; instead, they simply grant access to all ports greater than or equal to 1024. A policy file served via HTTP cannot authorize socket connections to ports under 1024. (However, note that due to a bug, this rule was not enforced prior to Flash Player Version 9.0.28.0.) To gain an HTTP-based policy file’s permission to perform a given socket connec- tion, we must manually load that policy file before attempting the connection, as shown in the following general code: Security.loadPolicyFile("http://domainOrIP/crossdomain.xml"); In the preceding code, domainOrIP is the exact domain or IP address of the desired socket connection. Once a request to load a policy file over HTTP has been issued, a follow-up request to connect to the desired port can immediately be issued. For example, suppose site-a.com has the following policy file posted on a web server at http://site-a.com/crossdomain.xml; the policy file authorizes site-b.com and www.site-b.com: To connect to port 9100 at site-a.com, any .swf file loaded from site-b.com or www.site-b.com would use the following code. // Request policy file via HTTP before making connection attempt Security.loadPolicyFile("http://site-a.com/crossdomain.xml"); var socket:Socket = new Socket( ); try { // Attempt connection (immediately after policy file has // been requested) socket.connect("site-a.com", 9100); } catch (e:SecurityError) { trace("Connection problem!"); trace(e.message); } When the preceding code runs, before allowing the requested connection to port 9100, Flash Player loads site-c.com’s policy file over HTTP. That policy file contains 444 | Chapter 19: Flash Player Security Restrictions the connecting .swf file’s origin as an authorized region, so the connection to port 9100 is then allowed to proceed. We’re now finished studying the ways in which a resource distributor can give for- eign .swf files permission to load data, access content as data, and connect to sock- ets. In the next section, we’ll continue our study of Flash Player’s permission mechanisms, examining how a .swf file’s creator can grant cross-scripting permis- sions to .swf files from foreign origins. Creator Permissions (allowDomain( )) We’ve just learned that distributor permissions are used to authorize accessing-con- tent-as-data, loading-data, and socket-connection operations. Distributor permis- sions are so named because they must be put in place by the distributor of the resource to which they grant access. By contrast, creator permissions are permissions put in place by the creator of a .swf file rather than its distributor. Creator permissions are more limited than distributor permissions; they affect cross-scripting and HTML-to-SWF scripting operations only. This book does not cover HTML-to-SWF-scripting operations. For details on security and HTML-to-SWF scripting, see the entries for the Security class’s static methods allowDomain() and allowInsecureDomain() in Adobe’s ActionScript Language Reference. Unlike distributor permissions, which are served independently of the content to which they grant access, creator permissions are issued from within .swf files. By call- ing Security.allowDomain( ) in a .swf file, a developer can grant .swf files from for- eign origins permission to cross-script that .swf file. For example, if app.swf includes the following line of code: Security.allowDomain("site-b.com"); then any .swf file loaded from site-b.com can cross-script app.swf. Furthermore, because the call to allowDomain( ) occurs within a .swf file, the permissions granted are effective no matter where that .swf file is posted. In contrast to distributor permissions, creator permissions travel with the .swf file in which they occur. The allowDomain( ) method has the following general form: Security.allowDomain("domainOrIP1", "domainOrIP2",..."domainOrIPn") Creator Permissions (allowDomain( )) | 445 where "domainOrIP1", "domainOrIP2",..."domainOrIPn" is a list of strings containing the domain names or IP addresses of authorized origins. A .swf file loaded from an authorized origin can perform cross-scripting operations on the .swf file that invoked allowDomain( ). As with policy files, the * character indicates a wildcard. For example, the following code authorizes all origins (i.e., any .swf file from any origin can cross-script the .swf file that contains the following line of code): Security.allowDomain("*"); To include the local realm as an authorized origin, allowDomain( ) must specify * (any origin) as an authorized domain. For example, a .swf file wishing to allow cross- scripting by local-with-networking .swf files must specify * as an authorized domain. However, when used with allowDomain( ), the * character cannot be used as a sub- domain wildcard. (This contrasts, somewhat confusingly, with policy file wildcard usage.) For example, the following code does not authorize all subdomains of example.com: // Warning: Do not use this code! Subdomain wildcards are not supported. Security.allowDomain("*.example.com"); Once an allowDomain( ) invocation completes, any .swf file from an authorized ori- gin can immediately perform authorized operations. For example, suppose a televi- sion network maintains a generic animation player application posted at www. sometvnetwork.com. The animation player loads animations in .swf-format from animation.sometvnetwork.com. To control the playback of the loaded animations, the animation player invokes basic MovieClip methods (play( ), stop( ), etc.) on them. Because the animation player and the animations it loads originate from different subdomains, the animation player must obtain permission to invoke MovieClip methods on the animations. Each animation’s main class constructor, hence, includes the following line of code, which gives the animation player the permission it needs: Security.allowDomain("www.sometvnetwork.com", "sometvnetwork.com"); Notice that because the animation player can be opened via www.sometvnetwork.com or sometvnetwork.com, the animation files grant permission to both domains. To load the ani- mations, the animation player uses the following code: var loader:Loader = new Loader( ); loader.load( new URLRequest("http://animation.sometvnetwork.com/animationName.swf")); As soon as each animation’s main class constructor method runs, the animation player can immediately begin controlling the loaded animation. 446 | Chapter 19: Flash Player Security Restrictions To ensure that cross-scripting permissions are applied immediately after a .swf file initializes, call Security.allowDomain( ) within that .swf file’s main class constructor method. A .swf file can determine whether it is currently authorized to cross-script a loaded .swf file by checking the childAllowsParent variable of the loaded .swf file’s LoaderInfo object. For more information on loading .swf files, see Chapter 28. For information on invoking movie clip methods on loaded .swf files, see the section “Compile-time Type Checking for Runtime-Loaded Assets” in Chapter 28. Allowing .swf Files Served Over HTTP to Cross-Script .swf Files Served Over HTTPS When a .swf file is served over HTTPS, Flash Player prevents allowDomain( ) call from granting authorization to non-HTTPS origins. However, developers wishing to authorize non-HTTPS origins from a .swf file served over HTTPS can, with due cau- tion, use Security.allowInsecureDomain( ). Authorizing a non-HTTPS origin from a .swf file loaded over HTTPS is considered dangerously insecure and is strongly discouraged. The syntaxand usage of allowInsecureDomain() is identical to that of allowDomain(),as discussed in the previous section. The allowInsecureDomain() method is different only in its ability to authorize non-HTTPS origins from a .swf file served over HTTPS. In the vast majority of situations, you should use allowDomain() rather than allowInsecureDomain() when issuing creator permissions. For a description of the special situations that call for the use of allowInsecureDomain(), see Security.allowInsecureDomain() in Adobe’s ActionScript Language Reference. Import Loading In Chapter 28, we’ll see how a parent .swf file can load a child .swf file in a spe- cial way that lets the parent use the child’s classes directly, as though they were defined by the parent. The technique requires that the parent .swf file import the child .swf file’s classes into its application domain. Here’s the basic code required in the parent .swf file (notice the use of the LoaderContext class’s instance vari- able applicationDomain): var loaderContext:LoaderContext = new LoaderContext( ); loaderContext.applicationDomain = ApplicationDomain.currentDomain; var loader:Loader = new Loader( ); Import Loading | 447 loader.load(new URLRequest("child.swf"), loaderContext); When the preceding code runs, the attempt to import the child’s classes into the par- ent’s application domain will be blocked by Flash Player’s security system in the fol- lowing situations: • If the parent .swf file and the child .swf file are loaded from different remote regions in the remote realm • If the parent .swf file is loaded from the local realm and has a different security- sandbox-type than the child .swf file In the first of the preceding cases, the distributor of the child .swf file can use a pol- icy file to give the parent .swf file permission to import the child .swf file’s classes. The steps required by the child .swf file’s distributor and the parent .swf file’s creator are as follows: 1. The child .swf file’s distributor must post a policy file authorizing the parent .swf file’s origin, as shown in the earlier section, “Distributor Permissions (Policy Files).” 2. If the policy file is not in the default location, the parent must load it manually with Security.loadPolicyFile( ), again, per the earlier section, “Distributor Permis- sions (Policy Files).” 3. When loading the child .swf file, the parent .swf file must pass load( ) a LoaderContext object whose securityDomain variable is set to flash.system. SecurityDomain.currentDomain. For example, suppose site-a.com has the following default policy file, which autho- rizes site-b.com and www.site-b.com: Now suppose site-b.com/parent.swf wants to import site-a.com/child.swf ’s classes into its application domain. To do so, site-b.com/parent.swf uses the following code (notice that Security.loadPolicyFile( ) is not used because the policy file is in the default policy file location): var loaderContext:LoaderContext = new LoaderContext( ); loaderContext.applicationDomain = ApplicationDomain.currentDomain; loaderContext.securityDomain = SecurityDomain.currentDomain; loader.load(new URLRequest("http://site-a.com/child.swf"), loaderContext); Using the securityDomain variable to gain distributor permission to import a .swf file’s classes into an application domain (as shown in the preceding code) is known as import loading. 448 | Chapter 19: Flash Player Security Restrictions Note that when a given .swf file, a.swf, uses import loading to load another .swf file, b.swf, Flash Player treats b.swf as though it were first copied to, and then loaded directly from a.swf’s server. Hence, b.swf adopts a.swf ’s security privileges, and b.swf ’s original security relationship with its actual origin is annulled. For example, b.swf file loses the ability to access resources from its actual origin via rel- ative URLs. Hence, when using import loading, always test whether the loaded . swf file continues to function as desired once loaded. Import loading is not required in the following situations because the parent .swf file is inherently permitted to import the child .swf file’s classes into its application domain: • A local .swf imports classes from another local .swf with the same security-sand- box-type. • A remote .swf imports classes from another remote .swf from the same remote region. For a full discussion of accessing classes in loaded .swf files, see the section “Com- pile-time Type Checking for Runtime-Loaded Assets” in Chapter 28 and see Chapter 31. Handling Security Violations Throughout this chapter we’ve seen a variety of security rules that govern a .swf file’s ability to perform various ActionScript operations. When an operation fails because it violates a security rule, ActionScript 3.0 either throws a SecurityError exception or dispatches a SecurityErrorEvent.SECURITY_ERROR. A SecurityError exception is thrown when an operation can immediately be judged to be in violation of a security rule. For example, if a local-with-filesystem .swf file attempts to open a socket connection, ActionScript immediately detects a security violation and throws a SecurityError exception. By contrast, a SecurityErrorEvent.SECURITY_ERROR event is dispatched when, after waiting for some asynchronous task to complete, ActionScript deems an operation in violation of a security rule. For example, when a local-with-networking .swf file uses the URLLoader class’s instance method load( ) to load a file from the remote realm, ActionScript must asynchronously check for a valid policy file authorizing the load operation. If the policy-file check fails, ActionScript dispatches a SecurityErrorEvent.SECURITY_ERROR event (note, not a SecurityError exception). In the debug version of Flash Player, uncaught SecurityError exceptions and unhan- dled SecurityErrorEvent.SECURITY_ERROR events are easy to spot; every time one occurs, Flash Player launches a dialog boxexplainingthe problem. By stark con- trast, in the release version of Flash Player, uncaught SecurityError exceptions and unhandled SecurityErrorEvent.SECURITY_ERROR events cause a silent failure that can be extremely difficult to diagnose. Handling Security Violations | 449 To ensure that no security violation goes unnoticed, always test code in the debug version of Flash Player. To handle security errors, we use the try/catch/finally statement. To handle SecurityErrorEvent.SECURITY_ERROR events, we use event listeners. For example, the following code generates a SecurityError by attempting to open a socket connection to a port above 65535. When the error occurs, the code adds a failure message to an onscreen TextField, output. var socket:Socket = new Socket( ); try { socket.connect("example.com", 70000); } catch (e:SecurityError) { output.appendText("Connection problem!\n"); output.appendText(e.message); } Similarly, by attempting to load a datafile from a web site that does not have a policy file, a local-with-networking .swf file containing the following code would cause a SecurityErrorEvent.SECURITY_ERROR event. Before attempting the load operation, the code registers an event listener that executes when the SecurityErrorEvent.SECURITY_ ERROR is dispatched. var urlloader:URLLoader = new URLLoader( ); // Register event listener urlloader.addEventListener(SecurityErrorEvent.SECURITY_ERROR, securityErrorListener); // Perform security violation urlloader.load(new URLRequest("http://www.example.com/index.xml")); As of the printing of this book, example.com does not have a policy file posted in the default location, and the preceding code, therefore, causes a SecurityErrorEvent.SECURITY_ERROR event. The event listener for the preceding SecurityErrorEvent.SECURITY_ERROR event, shown next, adds a failure message to an onscreen TextField, output: private function securityErrorListener (e:SecurityErrorEvent):void { output.appendText("Loading problem!\n"); output.appendText(e.text); } To determine whether a given operation can potentially generate a SecurityError exception or cause a SecurityErrorEvent.SECURITY_ERROR event, consult that opera- tion’s entry in Adobe’s ActionScript Language Reference. Each operation’s entry lists potential SecurityError exceptions under the heading “Throws” and potential SecurityErrorEvent.SECURITY_ERROR events under the heading “Events.” 450 | Chapter 19: Flash Player Security Restrictions In most cases, the class that defines the operation that generates a SecurityErrorEvent.SECURITY_ERROR event is also the class with which event listeners should be registered. For example, the URLLoader class defines the load( ) opera- tion, which has the potential to cause SecurityErrorEvent.SECURITY_ERROR events. Event listeners that handle SecurityErrorEvent.SECURITY_ERROR events caused by the URLLoader class’s instance method load( ), are registered with the URLLoader instance on which load( ) is invoked. The following code demonstrates: // When using URLLoader, register for events with the URLLoader instance. var urlloader:URLLoader = new URLLoader( ); urlloader.addEventListener(SecurityErrorEvent.SECURITY_ERROR, securityErrorListener); However, in some cases, the class that defines the operation that generates a SecurityErrorEvent.SECURITY_ERROR event is not also the class with which event lis- teners should be registered. For example, the Loader class defines the load( ) opera- tion, which has the potential to cause SecurityErrorEvent.SECURITY_ERROR events. But event listeners that handle those events must be registered with the LoaderInfo instance associated with the load( ) operation—not with the Loader instance on which load( ) was invoked. Again, the following code demonstrates: // When using Loader, register for events with the LoaderInfo instance. var loader:Loader = new Loader( ); loader.contentLoaderInfo.addEventListener(SecurityErrorEvent.SECURITY_ERROR, securityErrorListener); To determine the class with which SecurityErrorEvent.SECURITY_ERROR event listen- ers should be registered for any given operation, see Adobe’s ActionScript Language Reference. Specifically, look under the class description for the class that defines the operation causing the SecurityErrorEvent.SECURITY_ERROR event. Security Domains This section is intended to equip you with a basic understanding of the term security domain and its casual equivalent security sandbox, which is commonly used in Adobe’s documentation and other third-party resources. For reasons described in the next section this book avoids the use of both terms. Taken in combination, a given .swf file and the logical set of resources which that .swf file can freely access via accessing-content-as-data, loading-data, and cross-scripting operations (per Table 19-3 through Table 19-6) conceptually form a group known as a security domain. From the perspective of a .swf file from each of the four security-sand- box-types, Table 19-9 lists the constituents of that .swf file’s security domain. Don’t confuse security-sandbox-type with security domain.Asecurity- sandbox-type is a .swf file’s general security status, while a security domain is a logical set of resources. A .swf file’s security-sandbox-type actually determines its security domain, much as an employee’s corpo- rate rank might determine the accessible areas of a company building. Security Domains | 451 For the purposes of discussion, security domains are often described in regional terms, as a metaphorical safe zone. Therefore, a .swf file might be said to belong to, reside in, or be placed in its security domain. Likewise, a resource might be described as accessible to a .swf file because it belongs to that .swf file’s security domain. There are only four security-sandbox-types, but for each security-sandbox-type there are many security domains. For example, every .swf file in the remote realm has the same security-sandbox-type: remote. But a remote .swf file from site-a.com and a remote .swf file from site-b.com are part of two different security domains (one for site-a.com and one for site-b.com). Likewise, every .swf file in a trusted location of the local realm has the same security-sandbox-type: local-trusted. But two local- trusted .swf files from different corporate LANs are part of two different security domains (one for each LAN). Adobe’s documentation often uses the term security domain (and its casual equiva- lent security sandbox) when describing the resources that a .swf file can and cannot access. Ambiguous Use of the Term “Sandbox” As we learned in the previous section, both third-party literature on Flash Player security and Adobe’s documentation often use the term security sandboxor even simply sandboxas a casual equivalent of the formal term security domain. Further- more, in some rare cases, third-party literature and Adobe’s documentation also use the term security sandbox as a casual equivalent of the term security-sandbox-type. When reading security-related documentation outside this book, be aware that the term “sandbox” is normally used to mean security domain, and might, in some cases, be used to mean security-sandbox- type. To avoid similar confusion, this book forgoes the use of the casual terms security sandboxand sandboxentirely, and uses the official term security domain only when absolutely necessary (for example, when discussing the built-in SecurityDomain Table 19-9. Security domains by security-sandbox-type .swf file’s security-sandbox-type Security domain constituents Remote • Non-.swf resources from the .swf ’s region of origin • .swf files from the .swf’s region of origin Local-with-filesystem • Non-.swf resources in the local realm • local-with-filesystem .swf files in the local realm Local-with-networking • local-with-networking .swf files in the local realm Local-trusted • All non-.swf resources in the local and remote realms • local-trusted .swf files in the local realm 452 | Chapter 19: Flash Player Security Restrictions class). Rather than use those terms, this book always describes a .swf file’s security status relative to its security-sandbox-type. This book also lists the resources a .swf file can access explicitly, rather than using the general term security domain to describe a logical group of accessible resources. For example, consider the following sentence from Adobe’s Programming Action- Script 3.0 ➝ Flash Player APIs ➝ Flash Player Security ➝ Security sandboxes ➝ Local sandboxes: Local files that are registered as trusted are placed in the local-trusted sandbox. In the vocabulary preferred by this book, the preceding excerpt would read: Local files that are registered as trusted are assigned a security-sandbox-type of local- trusted. Next, consider this sentence, this time from Adobe’s Programming ActionScript 3.0 ➝ Flash Player APIs ➝ Flash Player Security ➝ Accessing loaded media as data: By default, a SWF file from one security sandbox cannot obtain pixel data or audio data from graphic or audio objects rendered or played by loaded media in another sandbox. In the vocabulary preferred by this book, the preceding excerpt would read: By default, a SWF file from one security domain cannot obtain pixel data or audio data from graphic or audio objects rendered or played by loaded media outside that security domain. In Adobe’s documentation and third-party sources, if the meaning of the term “sand- box” seems ambiguous, focus on the security-sandbox-type being discussed and the operation being allowed or prohibited. If all else fails, simply attempt to perform the operation you wish to perform, and rely on compiler and runtime security error mes- sages to determine if the operation is allowed. However, to be sure you encounter all possible security error messages, follow the guidance provided earlier in the section “Developers Automatically Trusted” and test in the debug version of Flash Player. Also remember that you can check a .swf file’s security-sandbox-type at runtime via the flash.system.Security.sandboxType variable. Knowing a .swf file’s security-sand- box-type will help you identify the security restrictions placed on that .swf file by Flash Player. Two Common Security-Related Development Issues Over the course of this chapter we’ve studied a variety of security restrictions and permissions systems. Let’s finish our study of Flash Player security by looking at two security scenarios that commonly occur in the typical ActionScript development pro- cess: accessing Internet subdomains and accessing the Loader class’s instance vari- able content. Each scenario presents a limitation and the corresponding workaround for that limitation. Two Common Security-Related Development Issues | 453 Accessing Internet Subdomains Earlier in Table 19-3, we learned that a remote .swf can load data from its remote region of origin only. In the section “The Local Realm, the Remote Realm, and Remote Regions,” we also learned that two different Internet subdomains, such as www.example.com and games.example.com are considered different remote regions. Hence, a .swf loaded from http://example.com can load any datafile posted at http://example.com, but cannot load datafiles posted on any other domain, including subdomains such as games.example.com. Perhaps surprisingly, this means that a .swf file loaded from http://example.com cannot use an absolute URL to access a file posted on www.example.com. To grant a .swf file loaded from example.com permission to load assets from www.example.com, use a policy file, as described in the earlier section “Distributor Permissions (Policy Files).” The following steps describe how the owner of example.com would supply a policy file allowing .swf files accessed via example.com to load datafiles from www.example. com, and vice versa. 1. Create a new text file named crossdomain.xml. 2. Open crossdomain.xml in a text editor. 3. Add the following XML code to the file: 4. Save crossdomain.xml. 5. Upload crossdomain.xml to the root directory of example.com (i.e., so that the file can be accessed at http://example.com/crossdomain.xml). Accessing the Loader Class’s Instance Variable Content When an external display asset is loaded using a Loader object, an instance of the loaded asset is placed in the Loader object’s content variable. As we learned in the earlier section “Accessing Content as Data,” accessing the content variable is consid- ered either an accessing-content-as-data operation (if the object contained by content is an image) or a cross-scripting operation (if the object contained by content is a .swf file’s main-class instance). Therefore, according to the security-sandbox-type restric- tions covered in Table 19-3, Table 19-4, and Table 19-5, accessing a loaded asset using content without appropriate permission will cause a security error in the fol- lowing situations: 454 | Chapter 19: Flash Player Security Restrictions • When a remote .swf file uses content to access a resource that originates from a different remote region • When a local-with-networking .swf file uses content to access a resource that originates from the remote realm • When a local-with-networking .swf file uses content to access a local-trusted .swf file • When a local-with-filesystem .swf file uses content to access a local-trusted .swf file If you face any of the preceding situations in your code, you should consider whether you can avoid using content entirely. If your application needs only to display the loaded asset on screen, then access to content is not required. To display a loaded asset onscreen without accessing content, simply add its Loader object—rather than the value of content—directly to the display list. For details and example code, see the section “Displaying the Loaded Asset On Screen” in Chapter 28. Note however, that in the following situations, content is required, and the appropri- ate creator or distributor permissions must be in place to avoid security violations: • When the .swf file that loaded the asset needs access to the asset’s data—for example, to read the pixels of a bitmap image • When the .swf file that loaded the asset needs to cross-script the loaded asset • When the loaded asset must be accessed directly as an object—for example, when the object representing the loaded asset must be passed to a method that expects an Bitmap object as an argument For more information on the Loader class, see Chapter 28. For more information on the display list, see Chapter 20. On to Part II! Over the past 19 chapters, we’ve examined most of ActionScript’s core concepts. In Part II, we’ll turn our attention to a specific part of the Flash runtime API known as the display API. There are lots of code examples and real-world programming scenar- ios to come, so get ready to apply your hard-earned knowledge of the core Action- Script language! PART II II. Display and Interactivity Part II explores techniques for displaying content on screen and responding to input events. Topics covered include the Flash runtime display API, hierarchical event han- dling, mouse and keyboard interactivity, animation, vector graphics, bitmap graphics, text, and content loading operations. When you complete Part II, you will be ready to add graphical content and interac- tivity to your own applications. Chapter 20, The Display API and the Display List Chapter 21, Events and Display Hierarchies Chapter 22, Interactivity Chapter 23, Screen Updates Chapter 24, Programmatic Animation Chapter 25, Drawing with Vectors Chapter 26, Bitmap Programming Chapter 27, Text Display and Input Chapter 28, Loading External Display Assets 457 Chapter 20 CHAPTER 20 The Display API and the Display List21 One of the primary activities of ActionScript programming is displaying things on the screen. Accordingly, the Flash platform provides a wide range of tools for creat- ing and manipulating graphical content. These tools can be broken into two general categories: • The Flash runtime display API, a set of classes for working with interactive visual objects, bitmaps, and vector content • Ready-made user interface components: • The Flex framework’s UI component set, a sophisticated collection of cus- tomizable user-interface widgets built on top of the display API • The Flash authoring tool’s UI component set, a collection of user-interface widgets with a smaller file size, lower memory usage, and fewer features than Flex framework’s UI component set The display API is built directly into all Flash runtimes and is, therefore, available to all .swf files. The display API is designed for producing highly customized user inter- faces or visual effects, such as those often found in motion graphics and games. This chapter focuses entirely on the display API. The Flexframework’s UI component set is part of the Flexframework, an external class library included with Adobe FlexBuilder and also available in standalone form for free at: http://www.adobe.com/go/flex2_sdk. The Flexframework’s UI component set is designed for building applications with relatively standard user interface con- trols (scrollbars, pull-down menus, data grids, etc.). The Flexframework’s interface widgets are typically used in MXML applications, but can also be included in prima- rily ActionScript-based applications. For details on using the Flexframework in ActionScript, see Chapter 30. The Flash authoring tool’s UI component set is designed for use with .swf files cre- ated in the Flash authoring tool, and for situations where file size and low memory usage are more important than advanced component features such as data binding and advanced styling options. The Flash authoring tool’s UI component set and the 458 | Chapter 20: The Display API and the Display List Flexframework’s UI component set share a very similar API, allowing developers to reuse knowledge when moving between the two component sets. In Flash Player 8 and older, ActionScript provided the following four basic building blocks for creating and managing visual content: Movie clip A container for graphical content, providing interactivity, primitive drawing, hierarchical layout, and animation feature Text field A rectangular region containing a formatted text Button An input control representing a very simple interactive “push button” Bitmap (introduced in Flash Player 8) A graphic in bitmap-format The preceding items continue to be available in the display API, but the classes repre- senting them in ActionScript 3.0 (MovieClip, TextField, SimpleButton, and Bitmap) have been enhanced and revised, and situated logically within a larger context. Display API Overview In ActionScript, all graphical content is created and manipulated using the classes in the display API. Even the interface widgets in the Flexframework and Flash author- ing tool component sets use the display API as a graphical foundation. Many display API classes directly represent a specific type of on-screen graphical content. For example, the Bitmap class represents bitmap graphics, the Sprite class represents interactive graphics, and the TextField class represents formatted text. For the pur- poses of discussion, we’ll refer to classes that directly represent on-screen content (and superclasses of such classes) as core display classes. The remaining classes in the display API define supplementary graphical information and functionality but do not, themselves, represent on-screen content. For example, the CapStyle and JointStyle classes define constants representing line-drawing preferences, while the Graphics and BitmapData classes define a variety of primitive drawing operations. We’ll refer to these nondisplay classes as supporting display classes. Whether core or supporting, most of the display API classes reside in the package flash.display. The core display classes, shown in Figure 20-1, are arranged in a class hierarchy that reflects three general tiers of functionality: display, user interactivity, and contain- ment. Accordingly, the three central classes in the display API are: DisplayObject, InteractiveObject, and DisplayObjectContainer. Those three classes cannot be instan- tiated directly but rather provide abstract functionality that is applied by various con- crete subclasses. Display API Overview | 459 As discussed in Chapter 6, ActionScript 3.0 does not support true abstract classes. Hence, in Figure 20-1, DisplayObject, InteractiveObject, and DisplayObjectContainer are listed not as abstract classes, but as abstract-style classes. However, despite this technicality, for the sake of brevity in the remainder of this chapter, we’ll use the shorter term “abstract” when referring to the architectural role played by DisplayObject, InteractiveObject, and DisplayObjectContainer. DisplayObject, the root of the core-display class hierarchy, defines the display API’s first tier of graphical functionality: on-screen display. All classes that inherit from DisplayObject gain a common set of fundamental graphical characteristics and capa- bilities. For example, every descendant of DisplayObject can be positioned, sized, and rotated with the variables x, y, width, height, and rotation. More than just a simple base class, DisplayObject is the source of many sophisticated capabilities in the display API, including (but not limited to): • Converting coordinates (see the DisplayObject class’s instance methods localToGlobal( ) and globalToLocal( ) in Adobe’s ActionScript Language Reference) • Checking intersections between objects and points (see the DisplayObject class’s instance methods hitTestObject( ) and hitTestPoint( ) in Adobe’s ActionScript Language Reference) Figure 20-1. Core-display class hierarchy DisplayObject InteractiveObject MorphShape StaticTextShape DisplayObjectContainer SimpleButtonTextField Sprite MovieClip LoaderStage BitmapVideo Concrete display class Authoring-tool only content Abstract-style class 460 | Chapter 20: The Display API and the Display List • Applying filters, transforms, and masks (see the DisplayObject class’s instance variables filters, transform, and mask in Adobe’s ActionScript Language Reference) • Scaling disproportionately for “stretchy” graphical layouts (see the DisplayObject class’s instance variable scale9grid in Adobe’s ActionScript Language Reference) Note that this book occasionally uses the informal term “display object” to mean any instance of a class descending from the DisplayObject class. DisplayObject’s direct concrete subclasses—Video, Bitmap, Shape, MorphShape, and StaticText—represent the simplest type of displayable content: basic on-screen graphics that cannot receive input or contain nested visual content. The Video class represents streaming video. The Bitmap class renders bitmap graphics created and manipulated with the supporting BitmapData class. The Shape class provides a sim- ple, lightweight canvas for vector drawing. And the special MorphShape and StaticText classes represent, respectively, shape tweens and static text created in the Flash authoring tool. Neither MorphShape nor StaticText can be instantiated with ActionScript. DisplayObject’s only abstract subclass, InteractiveObject, establishes the second tier of functionality in the display API: interactivity. All classes that inherit from InteractiveObject gain the ability to respond to input events from the user’s mouse and keyboard. InteractiveObject’s direct concrete subclasses—TextField and SimpleButton—represent two distinct kinds of interactive graphical content. The TextField class represents a rectangular area for displaying formatted text and receiv- ing text-based user input. The SimpleButton class represents Button symbols created in the Flash authoring tool and can also quickly create interactive buttons via Action- Script code. By responding to the input events broadcast by the TextField or SimpleButton, the programmer can add interactivity to an application. For example, a TextField instance can be programmed to change background color in response to a FocusEvent.FOCUS_IN event, and a SimpleButton instance can be programmed to submit a form in response to a MouseEvent.CLICK event. InteractiveObject’s only abstract subclass, DisplayObjectContainer, is the base of the third and final functional tier in the display API: containment. All classes that inherit from DisplayObjectContainer gain the ability to physically contain any other DisplayObject instance. Containers are used to group multiple visual objects so they can be manipulated as one. Any time a container is moved, rotated, or transformed, the objects it contains inherit that movement, rotation, or transformation. Likewise, any time a container is removed from the screen, the objects it contains are removed with it. Furthermore, containers can be nested within other containers to create hier- archical groups of display objects. When referring to the objects in a display hierar- chy, this book use standard tree-structure terminology; for example, an object that contains another object in a display hierarchy is referred to as that object’s parent, while the contained object is referred to as the parent’s child. In a multilevel display Display API Overview | 461 hierarchy, the objects above a given object in the hierarchy are referred to as the object’s ancestors. Conversely, the objects below a given object in the hierarchy are referred to as the object’s descendants. Finally, the top-level object in the hierarchy (the object from which all other objects descend) is referred to as the root object. Don’t confuse the ancestor objects and descendant objects in a dis- play hierarchy with the ancestor classes and descendant classes in an inheritance hierarchy. For clarity, this book occasionally uses the terms “display ancestors” and “display descendants” when referring to ancestor objects and descendant objects in a display hierarchy. DisplayObjectContainer’s subclasses—Sprite, MovieClip, Stage, and Loader—each provide a unique type of empty containment structure, waiting to be filled with con- tent. Sprite is the centerpiece of the container classes. As a descendant of both the InteractiveObject the DisplayObjectContainer classes, Sprite provides the perfect foun- dation for building custom user interface elements from scratch. The MovieClip class is an enhanced type of Sprite that represents animated content created in the Flash authoring tool. The Stage class represents the Flash runtime’s main display area (the viewable region within the borders of the application window). Finally, the Loader class is used to load external graphical content locally or over the Internet. Prior to ActionScript 3.0, the MovieClip class was used as an all-pur- pose graphics container (much like ActionScript 3.0’s Sprite class is used). As of ActionScript 3.0, MovieClip is used only to control instances of movie clip symbols created in the Flash authoring tool. Because ActionScript 3.0 does not provide a way to create timeline ele- ments such as frames and tweens, there is no need to create new empty movie clips at runtime in ActionScript 3.0. Instead, all program- matically created graphics should be instances of the appropriate core display class (Bitmap, Shape, Sprite, TextField, etc.). The display API provides a vast amount of functionality, dispersed over hundreds of methods and variables. While this book covers many of them, our focus in the com- ing chapters is on fundamental concepts rather than methodical coverage of each method and variable. For a dictionary-style reference to the display API, see Adobe’s ActionScript Language Reference. Extending the Core-Display Class Hierarchy While in many cases. the core display classes can productively be used without any modification, most nontrivial programs extend the functionality of the core display classes by creating subclasses suited to a custom purpose. For example, a geometric drawing program might define Ellipse, Rectangle, and Triangle classes that extend the Shape class. Similarly, a news viewer might define a Heading class that extends 462 | Chapter 20: The Display API and the Display List TextField, and a racing game might define a Car class that extends Sprite. In fact, the user interface widgets in the Flexframework are all descendants of the Sprite class. In the chapters ahead, we’ll encounter many examples of custom display classes. As you learn more about the core display classes, start thinking about how you could add to their functionality; ActionScript programmers are expected and encouraged to expand and enhance the core display classes with custom code. For more informa- tion, see the section “Custom Graphical Classes,” later in this chapter. The Display List As we’ve just discussed, the core display classes represent the types of graphical con- tent available in ActionScript. To create actual graphics from those theoretical types, we create instances of the core display classes and then add those instances to the display list. The display list is the hierarchy of all graphical objects currently dis- played by the Flash runtime. When a display object is added to the display list and is positioned in a visible area, the Flash runtime renders that display object’s content to the screen. The root of the display list is an instance of the Stage class, which is automatically created when the Flash runtime starts. This special, automatically created Stage instance serves two purposes. First, it acts as the outermost container for all graphi- cal content displayed in the Flash runtime (i.e., it is the root of the display list). Sec- ond, it provides information about, and control over, the global characteristics of the display area. For example, the Stage class’s instance variable quality indicates the rendering quality of all displayed graphics; scaleMode indicates how graphics scale when the display area is resized; and frameRate indicates the current preferred frames per second for all animations. As we’ll see throughout this chapter, the Stage instance is always accessed relative to some object on the display list via the DisplayObject class’s instance variable stage. For example, if output_txt is a TextField instance currently on the display list, then the Stage instance can be accessed using output_txt.stage. The Display List | 463 Prior to ActionScript 3.0, the Stage class did not contain objects on the display list. Furthermore, all Stage methods and variables were accessed via the Stage class directly, as in: trace(Stage.align); In ActionScript 3.0, Stage methods and variables are not accessed through the Stage class, and there is no global point of reference to the Stage instance. In ActionScript 3.0, the preceding line of code causes the following error: Access of possibly undefined property 'align' through a reference with static type 'Class' To avoid that error, access the Stage instance using the following approach: trace(someDisplayObj.stage.align); where someDisplayObj is an object currently on the display list. ActionScript 3.0’s Stage architecture allows for the future possibility of multiple Stage instances and also contributes to Flash Player’s security (because unauthorized externally-loaded objects have no global point of access to the Stage instance). Figure 20-2 depicts the state of the display list for an empty Flash runtime before any .swf file has been opened. The left side of the figure shows a symbolic representation of the Flash runtime, while the right side shows the corresponding display list hierar- chy. When the Flash runtime is empty, the display list hierarchy contains one item only (the lone Stage instance). But we’ll soon add more! When an empty Flash runtime opens a new .swf file, it locates that .swf file’s main class, creates an instance of it, and adds that instance to the display list as the Stage instance’s first child. Recall that a .swf file’s main class must inherit from either Sprite or MovieClip, both of which are descendants of DisplayObject. Tech- niques for specifying a .swf file’s main class are covered in Chapter 7. Figure 20-2. The display list for an empty Flash runtime Display List Stage instance Flash Player Stage instance 464 | Chapter 20: The Display API and the Display List The .swf file’s main class instance is both the program entry point and the first visual object displayed on screen. Even if the main class instance does not create any graph- ics itself, it is still added to the display list, ready to contain any graphics created by the program in the future. The main class instance of the first .swf file opened by the Flash runtime plays a special role in ActionScript; it determines certain global envi- ronment settings, such as relative-URL resolution and the type of security restric- tions applied to external operations. In honor of its special role, the main-class instance of the first .swf file opened by the Flash runtime is sometimes referred to as the “stage owner.” Let’s consider an example that shows how the stage owner is created. Suppose we start the standalone version of Flash Player and open a .swf file named GreetingApp. swf, whose main class is GreetingApp.IfGreetingApp.swf contains the class GreetingApp only, and GreetingApp creates no graphics, then Flash Player’s display list will contain just two items: the Stage instance and a GreetingApp instance (con- tained by the Stage instance). Figure 20-3 demonstrates. Once an instance of a .swf file’s main class has been added to the Stage instance, a program can add new content to the screen by following these general steps: 1. Create a displayable object (i.e., an instance of any core display class or any class that extends a core display class). 2. Invoke the DisplayObjectContainer class’s instance method addChild( ) on either the Stage instance or the main-class instance, and pass addChild( ) the display- able object created in Step 1. Let’s try out the preceding general steps by creating the GreetingApp class, then add- ing a rectangle, a circle, and a text field to the display list using addChild( ). First, here’s the skeleton of the GreetingApp class: package { import flash.display.*; import flash.text.TextField; Figure 20-3. The display list for GreetingApp.swf Flash Player Stage instance Display List Stage instance GreetingApp.swf’s GreetingApp instance GreetingApp.swf’s GreetingApp instance The Display List | 465 public class GreetingApp extends Sprite { public function GreetingApp ( ) { } } } Our GreetingApp class will use the Shape and Sprite classes, so it imports the entire flash.display package in which those classes reside. Likewise, GreetingApp will use the TextField class, so it imports flash.text.TextField. Notice that, by necessity, GreetingApp extends Sprite. GreetingApp must extend either Sprite or MovieClip because it is the program’s main class. In ActionScript 3.0, a .swf file’s main class must extend either Sprite or MovieClip, or a subclass of one of those classes. In cases where the main class represents the root timeline of a .fla file, it should extend MovieClip; in all other cases, it should extend Sprite. In our example, GreetingApp extends Sprite because it is not associated with a .fla file. It is intended to be compiled as a standalone ActionScript application. Now let’s create our rectangle and circle in GreetingApp’s constructor method. We’ll draw both the rectangle and the circle inside a single Shape object. Shape objects (and all graphical objects) are created with the new operator, just like any other kind of object. Here